diff options
author | Olli Salli <olli.salli@collabora.co.uk> | 2007-01-29 15:47:36 +0000 |
---|---|---|
committer | Olli Salli <olli.salli@collabora.co.uk> | 2007-01-29 15:47:36 +0000 |
commit | 7a897b917168505139de0c726e96663e2c7ed977 (patch) | |
tree | ae8ec0815c2f66152812bdc85439c44bc708cd67 | |
parent | 36e7ba52d4397763aca609c368bc6f2472b644a1 (diff) | |
download | telepathy-idle-7a897b917168505139de0c726e96663e2c7ed977.tar.gz |
Initial import (migration from SF.net SVN)
20070129154736-9db4d-be80727a61507e6581870228122d0d2a7c12995e.gz
90 files changed, 19205 insertions, 0 deletions
@@ -0,0 +1,5 @@ +Author: +Olli Salli <olli.salli@collabora.co.uk> + +Nokia contact: +Jussi Laako <jussi.laako@nokia.com> @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, 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 St, 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! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,236 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free +Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + +By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PREFIX', the package will +use PREFIX as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). Here is a another example: + + /bin/bash ./configure CONFIG_SHELL=/bin/bash + +Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent +configuration-related scripts to be executed by `/bin/bash'. + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..cc67d4a --- /dev/null +++ b/Makefile.am @@ -0,0 +1,4 @@ +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src data m4 + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..be01730 --- /dev/null +++ b/configure.ac @@ -0,0 +1,84 @@ +AC_PREREQ([2.59]) + +AC_INIT +AC_CONFIG_MACRO_DIR([m4]) + +AS_VERSION(telepathy-idle, TELEPATHY_IDLE_VERSION, 0, 0, 4, 0, WERROR="no", WERROR="no") + +AM_INIT_AUTOMAKE($PACKAGE, $VERSION) + +AM_PROG_LIBTOOL +AM_CONFIG_HEADER(config.h) + +dnl check for tools +AC_PROG_CC +AC_PROG_CC_STDC +AM_PROG_AS + +dnl decide on error flags +AS_COMPILER_FLAG(-Wall, WALL="yes", WALL="no") + +if test "x$WALL" = "xyes"; then + ERROR_CFLAGS="-Wall" + + if test "x$WERROR" = "xyes"; then + AS_COMPILER_FLAG(-Werror,ERROR_CFLAGS="$ERROR_CFLAGS -Werror",ERROR_CFLAGS="$ERROR_CFLAGS") + fi +fi + +AC_SUBST(ERROR_CFLAGS) + +AC_HEADER_STDC([]) +AC_C_INLINE + +dnl GTK docs +GTK_DOC_CHECK + +dnl Check for Glib +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.4, gobject-2.0 >= 2.4]) + +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0` +AC_SUBST(GLIB_GENMARSHAL) + +dnl Check for D-Bus +PKG_CHECK_MODULES(DBUS, [dbus-1 >= 0.51, dbus-glib-1 >= 0.51]) + +AC_SUBST(DBUS_CFLAGS) +AC_SUBST(DBUS_LIBS) + +dnl PKG_CHECK_MODULES(SOFIA_TOOLS, [sofia-tools >= 1.12], +dnl [ +dnl SOFIA_CFLAGS=$SOFIA_TOOLS_CFLAGS +dnl SOFIA_LIBS=$SOFIA_TOOLS_LIBS +dnl ], +dnl [ +dnl PKG_CHECK_MODULES(SOFIA_SIP_UA, [sofia-sip-ua >= 1.12], +dnl [PKG_CHECK_MODULES(SOFIA_SIP_UA_GLIB, [sofia-sip-ua-glib >= 1.12], +dnl [ +dnl SOFIA_CFLAGS="${SOFIA_SIP_UA_CFLAGS}${SOFIA_SIP_UA_GLIB_CFLAGS}" +dnl SOFIA_LIBS="${SOFIA_SIP_UA_LIBS}${SOFIA_SIP_UA_GLIB_LIBS}" +dnl ]])) +dnl ]) + +AC_SUBST(SOFIA_CFLAGS) +AC_SUBST(SOFIA_LIBS) + +dnl Check for OpenSSL +PKG_CHECK_MODULES(OPENSSL, [openssl >= 0.9.7]) + +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) + +AS_AC_EXPAND(DATADIR, $datadir) +DBUS_SERVICES_DIR="$DATADIR/dbus-1/services" +AC_SUBST(DBUS_SERVICES_DIR) +AC_DEFINE_UNQUOTED(DBUS_SERVICES_DIR, "$DBUS_SERVICES_DIR", [DBus services directory]) + +AC_OUTPUT( Makefile \ + src/Makefile \ + m4/Makefile \ + data/Makefile \ +) diff --git a/data/.git-darcs-dir b/data/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/data/.git-darcs-dir diff --git a/data/Makefile.am b/data/Makefile.am new file mode 100644 index 0000000..72b0a80 --- /dev/null +++ b/data/Makefile.am @@ -0,0 +1,19 @@ +# Telepathy manager file +managerdir = $(datadir)/telepathy/managers +manager_DATA = idle.manager + +# Dbus service file +BUILT_FILES = $(service_in_files:.service.in=.service) + +servicedir = $(DBUS_SERVICES_DIR) +service_in_files = org.freedesktop.Telepathy.ConnectionManager.idle.service.in +service_DATA = $(BUILT_FILES) +CLEANFILES = $(BUILT_FILES) + +# Rule to make the service file with bindir expanded +$(service_DATA): $(service_in_files) Makefile + @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ + +EXTRA_DIST= \ + ${service_in_files} \ + ${manager_DATA} diff --git a/data/idle.manager b/data/idle.manager new file mode 100644 index 0000000..b2cc6d1 --- /dev/null +++ b/data/idle.manager @@ -0,0 +1,19 @@ +[ConnectionManager] +Name = idle +BusName = org.freedesktop.Telepathy.ConnectionManager.idle +ObjectPath = /org/freedesktop/Telepathy/ConnectionManager/idle + +[Protocol irc] +param-account = s required register +param-server = s required register +param-fullname = s +param-port = q +param-password = s +param-charset = s +param-quit-message = s +param-use-ssl = b +default-port = 6667 +default-fullname = telepathy-idle user http://telepathy.freedesktop.org +default-charset = UTF-8 +default-quit-message = So long and thanks for all the IRC - telepathy-idle IRC Connection Manager for Telepathy - http://telepathy.freedesktop.org +default-use-ssl = false diff --git a/data/org.freedesktop.Telepathy.ConnectionManager.idle.service.in b/data/org.freedesktop.Telepathy.ConnectionManager.idle.service.in new file mode 100644 index 0000000..27afc08 --- /dev/null +++ b/data/org.freedesktop.Telepathy.ConnectionManager.idle.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.Telepathy.ConnectionManager.idle +Exec=@bindir@/telepathy-idle diff --git a/generate/.git-darcs-dir b/generate/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/generate/.git-darcs-dir diff --git a/generate/Makefile.am b/generate/Makefile.am new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/generate/Makefile.am diff --git a/generate/README b/generate/README new file mode 100644 index 0000000..0f00655 --- /dev/null +++ b/generate/README @@ -0,0 +1,21 @@ +Source code in this tree was originally generated from a magical gobject +generation tool called gengobject.py. It takes D-Bus XML introspection data and +outputs source, header and other files like signal marshallers. To read the XML +from generate/xml-modified/ directory, and output new source in the +generate/src/ directory, where changes can be reveiwed and applied to the src/ +directory by hand, run generate/do_src.sh. + +The XML is generated from the Python classes which are the current canonical +definition of the Telepathy interfaces, according to the definitions in +xml.def. Some manual modifications are necessary, to mark asynchronous methods, +and to remove the Introspect methods which are implemented automatically by the +bindings, so the pristine XML is kept in generate/xml-pristine/ and changed +applied by hand to generate/xml-modified/. To generate this pristine XML, run +generate/do_xml.sh. + +In both cases, applying the changes to the generated XML or src can be as easy +as: + darcs diff -u generate/src | patch -d src +Patch does a much better job of applying generated changes to the modified code +than darcs did when pulling patches from seperate trees (xml -> generate -> +live). diff --git a/generate/do_src.sh b/generate/do_src.sh new file mode 100644 index 0000000..08a0130 --- /dev/null +++ b/generate/do_src.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if [ `basename $PWD` == "generate" ]; then + TP=${TELEPATHY_PYTHON:=$PWD/../../telepathy-python} +else + TP=${TELEPATHY_PYTHON:=$PWD/../telepathy-python} +fi + +export PYTHONPATH=$TP:$PYTHONPATH + +test -d generate && cd generate +cd src + +echo Generating IdleConnectionManager files ... +python2.4 $TP/tools/gengobject.py ../xml-modified/idle-connection-manager.xml IdleConnectionManager + +echo Generating IdleConnection files ... +python2.4 $TP/tools/gengobject.py ../xml-modified/idle-connection.xml IdleConnection + +echo Generating IdleIMChannel files ... +python2.4 $TP/tools/gengobject.py ../xml-modified/idle-im-channel.xml IdleIMChannel + +echo Generating IdleMUCChannel files ... +python2.4 $TP/tools/gengobject.py ../xml-modified/idle-muc-channel.xml IdleMUCChannel + +echo Generating error enums ... +python2.4 $TP/tools/generrors.py diff --git a/generate/do_xml.sh b/generate/do_xml.sh new file mode 100644 index 0000000..fb20596 --- /dev/null +++ b/generate/do_xml.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +if [ `basename $PWD` == "generate" ]; then + TP=${TELEPATHY_PYTHON:=$PWD/../../telepathy-python} +else + TP=${TELEPATHY_PYTHON:=$PWD/../telepathy-python} +fi + +export PYTHONPATH=$TP:$PYTHONPATH + +test -d generate && cd generate +cd xml-pristine + +echo "Generating pristine XML in generate/xml-pristine..." +python2.4 $TP/tools/genxml.py ../idle.def diff --git a/generate/idle.def b/generate/idle.def new file mode 100644 index 0000000..4adb05b --- /dev/null +++ b/generate/idle.def @@ -0,0 +1,4 @@ +idle-connection-manager.xml IdleConnectionManager ConnectionManager +idle-connection.xml IdleConnection Connection, ConnectionInterfaceCapabilities, ConnectionInterfacePresence, ConnectionInterfaceRenaming +idle-im-channel.xml IdleIMChannel ChannelTypeText +idle-muc-channel.xml IdleMUCChannel ChannelTypeText, ChannelInterfaceGroup, ChannelInterfacePassword, PropertiesInterface diff --git a/generate/src/.git-darcs-dir b/generate/src/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/generate/src/.git-darcs-dir diff --git a/generate/src/idle-connection-manager-signals-marshal.list b/generate/src/idle-connection-manager-signals-marshal.list new file mode 100644 index 0000000..41e4027 --- /dev/null +++ b/generate/src/idle-connection-manager-signals-marshal.list @@ -0,0 +1 @@ +VOID:STRING,STRING,STRING diff --git a/generate/src/idle-connection-manager.c b/generate/src/idle-connection-manager.c new file mode 100644 index 0000000..a562b75 --- /dev/null +++ b/generate/src/idle-connection-manager.c @@ -0,0 +1,202 @@ +/* + * idle-connection-manager.c - Source for IdleConnectionManager + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <dbus/dbus-glib.h> +#include <stdio.h> +#include <stdlib.h> + +#include "idle-connection-manager.h" +#include "idle-connection-manager-signals-marshal.h" + +#include "idle-connection-manager-glue.h" + +G_DEFINE_TYPE(IdleConnectionManager, idle_connection_manager, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + NEW_CONNECTION, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleConnectionManagerPrivate IdleConnectionManagerPrivate; + +struct _IdleConnectionManagerPrivate +{ + gboolean dispose_has_run; +}; + +#define IDLE_CONNECTION_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerPrivate)) + +static void +idle_connection_manager_init (IdleConnectionManager *obj) +{ + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (obj); + + /* allocate any data required by the object here */ +} + +static void idle_connection_manager_dispose (GObject *object); +static void idle_connection_manager_finalize (GObject *object); + +static void +idle_connection_manager_class_init (IdleConnectionManagerClass *idle_connection_manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_connection_manager_class); + + g_type_class_add_private (idle_connection_manager_class, sizeof (IdleConnectionManagerPrivate)); + + object_class->dispose = idle_connection_manager_dispose; + object_class->finalize = idle_connection_manager_finalize; + + signals[NEW_CONNECTION] = + g_signal_new ("new-connection", + G_OBJECT_CLASS_TYPE (idle_connection_manager_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_manager_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, G_TYPE_STRING, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_connection_manager_class), &dbus_glib_idle_connection_manager_object_info); +} + +void +idle_connection_manager_dispose (GObject *object) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER (object); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + if (G_OBJECT_CLASS (idle_connection_manager_parent_class)->dispose) + G_OBJECT_CLASS (idle_connection_manager_parent_class)->dispose (object); +} + +void +idle_connection_manager_finalize (GObject *object) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER (object); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (self); + + /* free any data held directly by the object here */ + + G_OBJECT_CLASS (idle_connection_manager_parent_class)->finalize (object); +} + + + +/** + * idle_connection_manager_connect + * + * Implements DBus method Connect + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_connect (IdleConnectionManager *obj, const gchar * proto, GHashTable * parameters, gchar ** ret, gchar ** ret1, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_manager_get_mandatory_parameters + * + * Implements DBus method GetMandatoryParameters + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_get_mandatory_parameters (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_manager_get_optional_parameters + * + * Implements DBus method GetOptionalParameters + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_get_optional_parameters (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_manager_get_parameter_defaults + * + * Implements DBus method GetParameterDefaults + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_get_parameter_defaults (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_manager_list_protocols + * + * Implements DBus method ListProtocols + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_list_protocols (IdleConnectionManager *obj, gchar *** ret, GError **error) +{ + return TRUE; +} + diff --git a/generate/src/idle-connection-manager.h b/generate/src/idle-connection-manager.h new file mode 100644 index 0000000..715c5fe --- /dev/null +++ b/generate/src/idle-connection-manager.h @@ -0,0 +1,65 @@ +/* + * idle-connection-manager.h - Header for IdleConnectionManager + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __IDLE_CONNECTION_MANAGER_H__ +#define __IDLE_CONNECTION_MANAGER_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleConnectionManager IdleConnectionManager; +typedef struct _IdleConnectionManagerClass IdleConnectionManagerClass; + +struct _IdleConnectionManagerClass { + GObjectClass parent_class; +}; + +struct _IdleConnectionManager { + GObject parent; +}; + +GType idle_connection_manager_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_CONNECTION_MANAGER \ + (idle_connection_manager_get_type()) +#define IDLE_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManager)) +#define IDLE_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerClass)) +#define IDLE_IS_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_CONNECTION_MANAGER)) +#define IDLE_IS_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_CONNECTION_MANAGER)) +#define IDLE_CONNECTION_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerClass)) + + +gboolean idle_connection_manager_connect (IdleConnectionManager *obj, const gchar * proto, GHashTable * parameters, gchar ** ret, gchar ** ret1, GError **error); +gboolean idle_connection_manager_get_mandatory_parameters (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error); +gboolean idle_connection_manager_get_optional_parameters (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error); +gboolean idle_connection_manager_get_parameter_defaults (IdleConnectionManager *obj, const gchar * proto, GHashTable ** ret, GError **error); +gboolean idle_connection_manager_list_protocols (IdleConnectionManager *obj, gchar *** ret, GError **error); + + +G_END_DECLS + +#endif /* #ifndef __IDLE_CONNECTION_MANAGER_H__*/ diff --git a/generate/src/idle-connection-signals-marshal.list b/generate/src/idle-connection-signals-marshal.list new file mode 100644 index 0000000..2f728e5 --- /dev/null +++ b/generate/src/idle-connection-signals-marshal.list @@ -0,0 +1,5 @@ +VOID:INT,BOXED,BOXED +VOID:STRING,STRING,INT,INT,BOOLEAN +VOID:BOXED +VOID:INT,INT +VOID:INT,INT diff --git a/generate/src/idle-connection.c b/generate/src/idle-connection.c new file mode 100644 index 0000000..83af0a3 --- /dev/null +++ b/generate/src/idle-connection.c @@ -0,0 +1,521 @@ +/* + * idle-connection.c - Source for IdleConnection + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <dbus/dbus-glib.h> +#include <stdio.h> +#include <stdlib.h> + +#include "idle-connection.h" +#include "idle-connection-signals-marshal.h" + +#include "idle-connection-glue.h" + +G_DEFINE_TYPE(IdleConnection, idle_connection, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + CAPABILITIES_CHANGED, + NEW_CHANNEL, + PRESENCE_UPDATE, + RENAMED, + STATUS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleConnectionPrivate IdleConnectionPrivate; + +struct _IdleConnectionPrivate +{ + gboolean dispose_has_run; +}; + +#define IDLE_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_CONNECTION, IdleConnectionPrivate)) + +static void +idle_connection_init (IdleConnection *obj) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (obj); + + /* allocate any data required by the object here */ +} + +static void idle_connection_dispose (GObject *object); +static void idle_connection_finalize (GObject *object); + +static void +idle_connection_class_init (IdleConnectionClass *idle_connection_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_connection_class); + + g_type_class_add_private (idle_connection_class, sizeof (IdleConnectionPrivate)); + + object_class->dispose = idle_connection_dispose; + object_class->finalize = idle_connection_finalize; + + signals[CAPABILITIES_CHANGED] = + g_signal_new ("capabilities-changed", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_BOXED_BOXED, + G_TYPE_NONE, 3, G_TYPE_UINT, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID)))), (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID))))); + + signals[NEW_CHANNEL] = + g_signal_new ("new-channel", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__STRING_STRING_INT_INT_BOOLEAN, + G_TYPE_NONE, 5, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN); + + signals[PRESENCE_UPDATE] = + g_signal_new ("presence-update", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)))), G_TYPE_INVALID))))); + + signals[RENAMED] = + g_signal_new ("renamed", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[STATUS_CHANGED] = + g_signal_new ("status-changed", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_connection_class), &dbus_glib_idle_connection_object_info); +} + +void +idle_connection_dispose (GObject *object) +{ + IdleConnection *self = IDLE_CONNECTION (object); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + if (G_OBJECT_CLASS (idle_connection_parent_class)->dispose) + G_OBJECT_CLASS (idle_connection_parent_class)->dispose (object); +} + +void +idle_connection_finalize (GObject *object) +{ + IdleConnection *self = IDLE_CONNECTION (object); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (self); + + /* free any data held directly by the object here */ + + G_OBJECT_CLASS (idle_connection_parent_class)->finalize (object); +} + + + +/** + * idle_connection_add_status + * + * Implements DBus method AddStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_add_status (IdleConnection *obj, const gchar * status, GHashTable * parms, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_advertise_capabilities + * + * Implements DBus method AdvertiseCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_advertise_capabilities (IdleConnection *obj, const gchar ** add, const gchar ** remove, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_clear_status + * + * Implements DBus method ClearStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_clear_status (IdleConnection *obj, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_disconnect + * + * Implements DBus method Disconnect + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_disconnect (IdleConnection *obj, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_capabilities + * + * Implements DBus method GetCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_capabilities (IdleConnection *obj, guint handle, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_interfaces (IdleConnection *obj, gchar *** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_protocol + * + * Implements DBus method GetProtocol + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_protocol (IdleConnection *obj, gchar ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_self_handle + * + * Implements DBus method GetSelfHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_self_handle (IdleConnection *obj, guint* ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_status + * + * Implements DBus method GetStatus + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_status (IdleConnection *obj, guint* ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_get_statuses + * + * Implements DBus method GetStatuses + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_statuses (IdleConnection *obj, GHashTable ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_hold_handle + * + * Implements DBus method HoldHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_hold_handle (IdleConnection *obj, guint handle_type, guint handle, DBusGMethodInvocation *context) +{ + return TRUE; +} + + +/** + * idle_connection_inspect_handle + * + * Implements DBus method InspectHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_inspect_handle (IdleConnection *obj, guint handle_type, guint handle, gchar ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_list_channels + * + * Implements DBus method ListChannels + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_list_channels (IdleConnection *obj, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_release_handle + * + * Implements DBus method ReleaseHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_release_handle (IdleConnection *obj, guint handle_type, guint handle, DBusGMethodInvocation *context) +{ + return TRUE; +} + + +/** + * idle_connection_remove_status + * + * Implements DBus method RemoveStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_remove_status (IdleConnection *obj, const gchar * status, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_request_channel + * + * Implements DBus method RequestChannel + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_request_channel (IdleConnection *obj, const gchar * type, guint handle_type, guint handle, gboolean suppress_handler, gchar ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_request_handle + * + * Implements DBus method RequestHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_request_handle (IdleConnection *obj, guint handle_type, const gchar * name, DBusGMethodInvocation *context) +{ + return TRUE; +} + + +/** + * idle_connection_request_presence + * + * Implements DBus method RequestPresence + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_request_presence (IdleConnection *obj, const GArray * contacts, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_request_rename + * + * Implements DBus method RequestRename + * on interface org.freedesktop.Telepathy.Connection.Interface.Renaming + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_request_rename (IdleConnection *obj, const gchar * name, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_set_last_activity_time + * + * Implements DBus method SetLastActivityTime + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_set_last_activity_time (IdleConnection *obj, guint time, GError **error) +{ + return TRUE; +} + + +/** + * idle_connection_set_status + * + * Implements DBus method SetStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_set_status (IdleConnection *obj, GHashTable * statuses, GError **error) +{ + return TRUE; +} + diff --git a/generate/src/idle-connection.h b/generate/src/idle-connection.h new file mode 100644 index 0000000..4a503ad --- /dev/null +++ b/generate/src/idle-connection.h @@ -0,0 +1,81 @@ +/* + * idle-connection.h - Header for IdleConnection + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __IDLE_CONNECTION_H__ +#define __IDLE_CONNECTION_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleConnection IdleConnection; +typedef struct _IdleConnectionClass IdleConnectionClass; + +struct _IdleConnectionClass { + GObjectClass parent_class; +}; + +struct _IdleConnection { + GObject parent; +}; + +GType idle_connection_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_CONNECTION \ + (idle_connection_get_type()) +#define IDLE_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_CONNECTION, IdleConnection)) +#define IDLE_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_CONNECTION, IdleConnectionClass)) +#define IDLE_IS_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_CONNECTION)) +#define IDLE_IS_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_CONNECTION)) +#define IDLE_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_CONNECTION, IdleConnectionClass)) + + +gboolean idle_connection_add_status (IdleConnection *obj, const gchar * status, GHashTable * parms, GError **error); +gboolean idle_connection_advertise_capabilities (IdleConnection *obj, const gchar ** add, const gchar ** remove, GError **error); +gboolean idle_connection_clear_status (IdleConnection *obj, GError **error); +gboolean idle_connection_disconnect (IdleConnection *obj, GError **error); +gboolean idle_connection_get_capabilities (IdleConnection *obj, guint handle, GPtrArray ** ret, GError **error); +gboolean idle_connection_get_interfaces (IdleConnection *obj, gchar *** ret, GError **error); +gboolean idle_connection_get_protocol (IdleConnection *obj, gchar ** ret, GError **error); +gboolean idle_connection_get_self_handle (IdleConnection *obj, guint* ret, GError **error); +gboolean idle_connection_get_status (IdleConnection *obj, guint* ret, GError **error); +gboolean idle_connection_get_statuses (IdleConnection *obj, GHashTable ** ret, GError **error); +gboolean idle_connection_hold_handle (IdleConnection *obj, guint handle_type, guint handle, DBusGMethodInvocation *context); +gboolean idle_connection_inspect_handle (IdleConnection *obj, guint handle_type, guint handle, gchar ** ret, GError **error); +gboolean idle_connection_list_channels (IdleConnection *obj, GPtrArray ** ret, GError **error); +gboolean idle_connection_release_handle (IdleConnection *obj, guint handle_type, guint handle, DBusGMethodInvocation *context); +gboolean idle_connection_remove_status (IdleConnection *obj, const gchar * status, GError **error); +gboolean idle_connection_request_channel (IdleConnection *obj, const gchar * type, guint handle_type, guint handle, gboolean suppress_handler, gchar ** ret, GError **error); +gboolean idle_connection_request_handle (IdleConnection *obj, guint handle_type, const gchar * name, DBusGMethodInvocation *context); +gboolean idle_connection_request_presence (IdleConnection *obj, const GArray * contacts, GError **error); +gboolean idle_connection_request_rename (IdleConnection *obj, const gchar * name, GError **error); +gboolean idle_connection_set_last_activity_time (IdleConnection *obj, guint time, GError **error); +gboolean idle_connection_set_status (IdleConnection *obj, GHashTable * statuses, GError **error); + + +G_END_DECLS + +#endif /* #ifndef __IDLE_CONNECTION_H__*/ diff --git a/generate/src/idle-im-channel-signals-marshal.list b/generate/src/idle-im-channel-signals-marshal.list new file mode 100644 index 0000000..b974b8a --- /dev/null +++ b/generate/src/idle-im-channel-signals-marshal.list @@ -0,0 +1,3 @@ +VOID:VOID +VOID:INT,INT,INT,INT,STRING +VOID:INT,INT,STRING diff --git a/generate/src/idle-im-channel.c b/generate/src/idle-im-channel.c new file mode 100644 index 0000000..a5bf693 --- /dev/null +++ b/generate/src/idle-im-channel.c @@ -0,0 +1,258 @@ +/* + * idle-im-channel.c - Source for IdleIMChannel + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <dbus/dbus-glib.h> +#include <stdio.h> +#include <stdlib.h> + +#include "idle-im-channel.h" +#include "idle-im-channel-signals-marshal.h" + +#include "idle-im-channel-glue.h" + +G_DEFINE_TYPE(IdleIMChannel, idle_im_channel, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + CLOSED, + RECEIVED, + SENT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleIMChannelPrivate IdleIMChannelPrivate; + +struct _IdleIMChannelPrivate +{ + gboolean dispose_has_run; +}; + +#define IDLE_IM_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_IM_CHANNEL, IdleIMChannelPrivate)) + +static void +idle_im_channel_init (IdleIMChannel *obj) +{ + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (obj); + + /* allocate any data required by the object here */ +} + +static void idle_im_channel_dispose (GObject *object); +static void idle_im_channel_finalize (GObject *object); + +static void +idle_im_channel_class_init (IdleIMChannelClass *idle_im_channel_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_im_channel_class); + + g_type_class_add_private (idle_im_channel_class, sizeof (IdleIMChannelPrivate)); + + object_class->dispose = idle_im_channel_dispose; + object_class->finalize = idle_im_channel_finalize; + + signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[RECEIVED] = + g_signal_new ("received", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__INT_INT_INT_INT_STRING, + G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SENT] = + g_signal_new ("sent", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__INT_INT_STRING, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_im_channel_class), &dbus_glib_idle_im_channel_object_info); +} + +void +idle_im_channel_dispose (GObject *object) +{ + IdleIMChannel *self = IDLE_IM_CHANNEL (object); + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + if (G_OBJECT_CLASS (idle_im_channel_parent_class)->dispose) + G_OBJECT_CLASS (idle_im_channel_parent_class)->dispose (object); +} + +void +idle_im_channel_finalize (GObject *object) +{ + IdleIMChannel *self = IDLE_IM_CHANNEL (object); + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (self); + + /* free any data held directly by the object here */ + + G_OBJECT_CLASS (idle_im_channel_parent_class)->finalize (object); +} + + + +/** + * idle_im_channel_acknowledge_pending_message + * + * Implements DBus method AcknowledgePendingMessage + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_acknowledge_pending_message (IdleIMChannel *obj, guint id, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_close + * + * Implements DBus method Close + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_close (IdleIMChannel *obj, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_get_channel_type + * + * Implements DBus method GetChannelType + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_channel_type (IdleIMChannel *obj, gchar ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_get_handle + * + * Implements DBus method GetHandle + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_handle (IdleIMChannel *obj, guint* ret, guint* ret1, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_interfaces (IdleIMChannel *obj, gchar *** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_list_pending_messages + * + * Implements DBus method ListPendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_list_pending_messages (IdleIMChannel *obj, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_im_channel_send + * + * Implements DBus method Send + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_send (IdleIMChannel *obj, guint type, const gchar * text, GError **error) +{ + return TRUE; +} + diff --git a/generate/src/idle-im-channel.h b/generate/src/idle-im-channel.h new file mode 100644 index 0000000..6623dbf --- /dev/null +++ b/generate/src/idle-im-channel.h @@ -0,0 +1,67 @@ +/* + * idle-im-channel.h - Header for IdleIMChannel + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __IDLE_IM_CHANNEL_H__ +#define __IDLE_IM_CHANNEL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleIMChannel IdleIMChannel; +typedef struct _IdleIMChannelClass IdleIMChannelClass; + +struct _IdleIMChannelClass { + GObjectClass parent_class; +}; + +struct _IdleIMChannel { + GObject parent; +}; + +GType idle_im_channel_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_IM_CHANNEL \ + (idle_im_channel_get_type()) +#define IDLE_IM_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_IM_CHANNEL, IdleIMChannel)) +#define IDLE_IM_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_IM_CHANNEL, IdleIMChannelClass)) +#define IDLE_IS_IM_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_IM_CHANNEL)) +#define IDLE_IS_IM_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_IM_CHANNEL)) +#define IDLE_IM_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_IM_CHANNEL, IdleIMChannelClass)) + + +gboolean idle_im_channel_acknowledge_pending_message (IdleIMChannel *obj, guint id, GError **error); +gboolean idle_im_channel_close (IdleIMChannel *obj, GError **error); +gboolean idle_im_channel_get_channel_type (IdleIMChannel *obj, gchar ** ret, GError **error); +gboolean idle_im_channel_get_handle (IdleIMChannel *obj, guint* ret, guint* ret1, GError **error); +gboolean idle_im_channel_get_interfaces (IdleIMChannel *obj, gchar *** ret, GError **error); +gboolean idle_im_channel_list_pending_messages (IdleIMChannel *obj, GPtrArray ** ret, GError **error); +gboolean idle_im_channel_send (IdleIMChannel *obj, guint type, const gchar * text, GError **error); + + +G_END_DECLS + +#endif /* #ifndef __IDLE_IM_CHANNEL_H__*/ diff --git a/generate/src/idle-muc-channel-signals-marshal.list b/generate/src/idle-muc-channel-signals-marshal.list new file mode 100644 index 0000000..a81dede --- /dev/null +++ b/generate/src/idle-muc-channel-signals-marshal.list @@ -0,0 +1,10 @@ +VOID:VOID +VOID:INT,INT +VOID:VOID +VOID:STRING,BOXED,BOXED,BOXED,BOXED +VOID:INT,INT +VOID:BOXED +VOID:BOXED +VOID:INT,INT,INT,INT,STRING +VOID:INT,INT,INT,STRING +VOID:INT,INT,STRING diff --git a/generate/src/idle-muc-channel.c b/generate/src/idle-muc-channel.c new file mode 100644 index 0000000..ae814d2 --- /dev/null +++ b/generate/src/idle-muc-channel.c @@ -0,0 +1,595 @@ +/* + * idle-muc-channel.c - Source for IdleMUCChannel + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <dbus/dbus-glib.h> +#include <stdio.h> +#include <stdlib.h> + +#include "idle-muc-channel.h" +#include "idle-muc-channel-signals-marshal.h" + +#include "idle-muc-channel-glue.h" + +G_DEFINE_TYPE(IdleMUCChannel, idle_muc_channel, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + CLOSED, + GROUP_FLAGS_CHANGED, + LOST_MESSAGE, + MEMBERS_CHANGED, + PASSWORD_FLAGS_CHANGED, + PROPERTIES_CHANGED, + PROPERTY_FLAGS_CHANGED, + RECEIVED, + SEND_ERROR, + SENT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleMUCChannelPrivate IdleMUCChannelPrivate; + +struct _IdleMUCChannelPrivate +{ + gboolean dispose_has_run; +}; + +#define IDLE_MUC_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelPrivate)) + +static void +idle_muc_channel_init (IdleMUCChannel *obj) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (obj); + + /* allocate any data required by the object here */ +} + +static void idle_muc_channel_dispose (GObject *object); +static void idle_muc_channel_finalize (GObject *object); + +static void +idle_muc_channel_class_init (IdleMUCChannelClass *idle_muc_channel_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_muc_channel_class); + + g_type_class_add_private (idle_muc_channel_class, sizeof (IdleMUCChannelPrivate)); + + object_class->dispose = idle_muc_channel_dispose; + object_class->finalize = idle_muc_channel_finalize; + + signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[GROUP_FLAGS_CHANGED] = + g_signal_new ("group-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[LOST_MESSAGE] = + g_signal_new ("lost-message", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[MEMBERS_CHANGED] = + g_signal_new ("members-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__STRING_BOXED_BOXED_BOXED_BOXED, + G_TYPE_NONE, 5, G_TYPE_STRING, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY); + + signals[PASSWORD_FLAGS_CHANGED] = + g_signal_new ("password-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[PROPERTIES_CHANGED] = + g_signal_new ("properties-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_VALUE, G_TYPE_INVALID))))); + + signals[PROPERTY_FLAGS_CHANGED] = + g_signal_new ("property-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID))))); + + signals[RECEIVED] = + g_signal_new ("received", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_INT_INT_STRING, + G_TYPE_NONE, 5, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SEND_ERROR] = + g_signal_new ("send-error", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_INT_STRING, + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SENT] = + g_signal_new ("sent", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_STRING, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_muc_channel_class), &dbus_glib_idle_muc_channel_object_info); +} + +void +idle_muc_channel_dispose (GObject *object) +{ + IdleMUCChannel *self = IDLE_MUC_CHANNEL (object); + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + if (G_OBJECT_CLASS (idle_muc_channel_parent_class)->dispose) + G_OBJECT_CLASS (idle_muc_channel_parent_class)->dispose (object); +} + +void +idle_muc_channel_finalize (GObject *object) +{ + IdleMUCChannel *self = IDLE_MUC_CHANNEL (object); + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (self); + + /* free any data held directly by the object here */ + + G_OBJECT_CLASS (idle_muc_channel_parent_class)->finalize (object); +} + + + +/** + * idle_muc_channel_acknowledge_pending_message + * + * Implements DBus method AcknowledgePendingMessage + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_acknowledge_pending_message (IdleMUCChannel *obj, guint id, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_add_members + * + * Implements DBus method AddMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_add_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_close + * + * Implements DBus method Close + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_close (IdleMUCChannel *obj, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_all_members + * + * Implements DBus method GetAllMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_all_members (IdleMUCChannel *obj, GArray ** ret, GArray ** ret1, GArray ** ret2, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_channel_type + * + * Implements DBus method GetChannelType + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_channel_type (IdleMUCChannel *obj, gchar ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_group_flags + * + * Implements DBus method GetGroupFlags + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_group_flags (IdleMUCChannel *obj, guint* ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_handle + * + * Implements DBus method GetHandle + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_handle (IdleMUCChannel *obj, guint* ret, guint* ret1, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_handle_owners + * + * Implements DBus method GetHandleOwners + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_handle_owners (IdleMUCChannel *obj, const GArray * handles, GArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_interfaces (IdleMUCChannel *obj, gchar *** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_local_pending_members + * + * Implements DBus method GetLocalPendingMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_local_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_members + * + * Implements DBus method GetMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_message_types + * + * Implements DBus method GetMessageTypes + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_message_types (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_password_flags + * + * Implements DBus method GetPasswordFlags + * on interface org.freedesktop.Telepathy.Channel.Interface.Password + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_password_flags (IdleMUCChannel *obj, guint* ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_properties + * + * Implements DBus method GetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_properties (IdleMUCChannel *obj, const GArray * properties, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_remote_pending_members + * + * Implements DBus method GetRemotePendingMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_remote_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_get_self_handle + * + * Implements DBus method GetSelfHandle + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_self_handle (IdleMUCChannel *obj, guint* ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_list_pending_messages + * + * Implements DBus method ListPendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_list_pending_messages (IdleMUCChannel *obj, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_list_properties + * + * Implements DBus method ListProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_list_properties (IdleMUCChannel *obj, GPtrArray ** ret, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_provide_password + * + * Implements DBus method ProvidePassword + * on interface org.freedesktop.Telepathy.Channel.Interface.Password + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_muc_channel_provide_password (IdleMUCChannel *obj, const gchar * password, DBusGMethodInvocation *context) +{ + return TRUE; +} + + +/** + * idle_muc_channel_remove_members + * + * Implements DBus method RemoveMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_remove_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_send + * + * Implements DBus method Send + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_send (IdleMUCChannel *obj, guint type, const gchar * text, GError **error) +{ + return TRUE; +} + + +/** + * idle_muc_channel_set_properties + * + * Implements DBus method SetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_set_properties (IdleMUCChannel *obj, const GPtrArray * properties, GError **error) +{ + return TRUE; +} + diff --git a/generate/src/idle-muc-channel.h b/generate/src/idle-muc-channel.h new file mode 100644 index 0000000..76b635a --- /dev/null +++ b/generate/src/idle-muc-channel.h @@ -0,0 +1,82 @@ +/* + * idle-muc-channel.h - Header for IdleMUCChannel + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __IDLE_MUC_CHANNEL_H__ +#define __IDLE_MUC_CHANNEL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleMUCChannel IdleMUCChannel; +typedef struct _IdleMUCChannelClass IdleMUCChannelClass; + +struct _IdleMUCChannelClass { + GObjectClass parent_class; +}; + +struct _IdleMUCChannel { + GObject parent; +}; + +GType idle_muc_channel_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_MUC_CHANNEL \ + (idle_muc_channel_get_type()) +#define IDLE_MUC_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannel)) +#define IDLE_MUC_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelClass)) +#define IDLE_IS_MUC_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_MUC_CHANNEL)) +#define IDLE_IS_MUC_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_MUC_CHANNEL)) +#define IDLE_MUC_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelClass)) + + +gboolean idle_muc_channel_acknowledge_pending_message (IdleMUCChannel *obj, guint id, GError **error); +gboolean idle_muc_channel_add_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error); +gboolean idle_muc_channel_close (IdleMUCChannel *obj, GError **error); +gboolean idle_muc_channel_get_all_members (IdleMUCChannel *obj, GArray ** ret, GArray ** ret1, GArray ** ret2, GError **error); +gboolean idle_muc_channel_get_channel_type (IdleMUCChannel *obj, gchar ** ret, GError **error); +gboolean idle_muc_channel_get_group_flags (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_get_handle (IdleMUCChannel *obj, guint* ret, guint* ret1, GError **error); +gboolean idle_muc_channel_get_handle_owners (IdleMUCChannel *obj, const GArray * handles, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_interfaces (IdleMUCChannel *obj, gchar *** ret, GError **error); +gboolean idle_muc_channel_get_local_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_message_types (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_password_flags (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_get_properties (IdleMUCChannel *obj, const GArray * properties, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_get_remote_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_self_handle (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_list_pending_messages (IdleMUCChannel *obj, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_list_properties (IdleMUCChannel *obj, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_provide_password (IdleMUCChannel *obj, const gchar * password, DBusGMethodInvocation *context); +gboolean idle_muc_channel_remove_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error); +gboolean idle_muc_channel_send (IdleMUCChannel *obj, guint type, const gchar * text, GError **error); +gboolean idle_muc_channel_set_properties (IdleMUCChannel *obj, const GPtrArray * properties, GError **error); + + +G_END_DECLS + +#endif /* #ifndef __IDLE_MUC_CHANNEL_H__*/ diff --git a/generate/src/telepathy-errors.h b/generate/src/telepathy-errors.h new file mode 100644 index 0000000..972dadb --- /dev/null +++ b/generate/src/telepathy-errors.h @@ -0,0 +1,61 @@ +/* + * telepathy-errors.h - Header for Telepathy error types + * Copyright (C) 2005 Collabora Ltd. + * Copyright (C) 2005 Nokia Corporation + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __TELEPATHY_ERRORS_H__ +#define __TELEPATHY_ERRORS_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef enum +{ + ChannelInviteOnly, /** The requested channel is invite only. + */ + InvalidHandle, /** The contact name specified is unknown on this channel + * or connection. + */ + ChannelBanned, /** You are banned from the channel. + */ + Disconnected, /** The connection is not currently connected and cannot + * be used. + */ + InvalidArgument, /** Raised when one of the provided arguments is invalid. + */ + NetworkError, /** Raised when there is an error reading from or writing + * to the network. + */ + PermissionDenied, /** The user is not permitted to perform the requested + * operation. + */ + ChannelFull, /** The channel is full. + */ + NotAvailable, /** Raised when the requested functionality is temporarily + * unavailable. + */ + NotImplemented, /** Raised when the requested method, channel, etc is not + * available on this connection. + */ +} TelepathyErrors; + + +G_END_DECLS + +#endif /* #ifndef __TELEPATHY_ERRORS_H__*/ diff --git a/generate/xml-modified/.git-darcs-dir b/generate/xml-modified/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/generate/xml-modified/.git-darcs-dir diff --git a/generate/xml-modified/idle-connection-manager.xml b/generate/xml-modified/idle-connection-manager.xml new file mode 100644 index 0000000..ff2422f --- /dev/null +++ b/generate/xml-modified/idle-connection-manager.xml @@ -0,0 +1,23 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/IdleConnectionManager"> + <interface name="org.freedesktop.Telepathy.ConnectionManager"> + <method name="ListProtocols"> + <arg direction="out" type="as" /> + </method> + <signal name="NewConnection"> + <arg type="s" name="bus_name" /> + <arg type="o" name="object_path" /> + <arg type="s" name="proto" /> + </signal> + <method name="RequestConnection"> + <arg direction="in" type="s" name="proto" /> + <arg direction="in" type="a{sv}" name="parameters" /> + <arg direction="out" type="s" /> + <arg direction="out" type="o" /> + </method> + <method name="GetParameters"> + <arg direction="in" type="s" name="proto" /> + <arg direction="out" type="a(susv)" /> + </method> + </interface> +</node> diff --git a/generate/xml-modified/idle-connection.xml b/generate/xml-modified/idle-connection.xml new file mode 100644 index 0000000..55fe633 --- /dev/null +++ b/generate/xml-modified/idle-connection.xml @@ -0,0 +1,100 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/IdleConnection"> + <interface name="org.freedesktop.Telepathy.Connection"> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + <method name="Connect"> + </method> + <method name="Disconnect"> + </method> + <method name="InspectHandles"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" type="u" name="handle_type" /> + <arg direction="in" type="au" name="handles" /> + <arg direction="out" type="as" /> + </method> + <method name="ReleaseHandles"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" type="u" name="handle_type" /> + <arg direction="in" type="au" name="handle" /> + </method> + <method name="RequestChannel"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" type="s" name="type" /> + <arg direction="in" type="u" name="handle_type" /> + <arg direction="in" type="u" name="handle" /> + <arg direction="in" type="b" name="suppress_handler" /> + <arg direction="out" type="o" /> + </method> + <method name="RequestHandles"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" type="u" name="handle_type" /> + <arg direction="in" type="as" name="name" /> + <arg direction="out" type="au" /> + </method> + <method name="GetProtocol"> + <arg direction="out" type="s" /> + </method> + <method name="GetStatus"> + <arg direction="out" type="u" /> + </method> + <method name="ListChannels"> + <arg direction="out" type="a(osuu)" /> + </method> + <method name="HoldHandles"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" type="u" name="handle_type" /> + <arg direction="in" type="au" name="handle" /> + </method> + <method name="GetSelfHandle"> + <arg direction="out" type="u" /> + </method> + <signal name="NewChannel"> + <arg type="o" name="object_path" /> + <arg type="s" name="channel_type" /> + <arg type="u" name="handle_type" /> + <arg type="u" name="handle" /> + <arg type="b" name="suppress_handler" /> + </signal> + <signal name="StatusChanged"> + <arg type="u" name="status" /> + <arg type="u" name="reason" /> + </signal> + </interface> + <interface name="org.freedesktop.Telepathy.Connection.Interface.Presence"> + <method name="RemoveStatus"> + <arg direction="in" type="s" name="status" /> + </method> + <method name="ClearStatus"> + </method> + <method name="SetLastActivityTime"> + <arg direction="in" type="u" name="time" /> + </method> + <method name="AddStatus"> + <arg direction="in" type="s" name="status" /> + <arg direction="in" type="a{sv}" name="parms" /> + </method> + <method name="RequestPresence"> + <arg direction="in" type="au" name="contacts" /> + </method> + <method name="GetStatuses"> + <arg direction="out" type="a{s(ubba{ss})}" /> + </method> + <signal name="PresenceUpdate"> + <arg type="a{u(ua{sa{sv}})}" name="presence" /> + </signal> + <method name="SetStatus"> + <arg direction="in" type="a{sa{sv}}" name="statuses" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Connection.Interface.Renaming"> + <signal name="Renamed"> + <arg name="original" type="u" /> + <arg name="new" type="u" /> + </signal> + <method name="RequestRename"> + <arg direction="in" name="name" type="s" /> + </method> + </interface> +</node> diff --git a/generate/xml-modified/idle-im-channel.xml b/generate/xml-modified/idle-im-channel.xml new file mode 100644 index 0000000..eda1a7d --- /dev/null +++ b/generate/xml-modified/idle-im-channel.xml @@ -0,0 +1,51 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node name="/IdleIMChannel"> + <interface name="org.freedesktop.Telepathy.Channel.Type.Text"> + <signal name="Received"> + <arg type="u" name="id" /> + <arg type="u" name="timestamp" /> + <arg type="u" name="sender" /> + <arg type="u" name="type" /> + <arg type="u" name="flags" /> + <arg type="s" name="text" /> + </signal> + <method name="ListPendingMessages"> + <arg direction="in" type="b" name="clear" /> + <arg direction="out" type="a(uuuuus)" /> + </method> + <method name="AcknowledgePendingMessages"> + <arg direction="in" type="au" name="ids" /> + </method> + <method name="Send"> + <arg direction="in" type="u" name="type" /> + <arg direction="in" type="s" name="text" /> + </method> + <signal name="Sent"> + <arg type="u" name="timestamp" /> + <arg type="u" name="type" /> + <arg type="s" name="text" /> + </signal> + <signal name="SendError"> + <arg type="u" name="error" /> + <arg type="u" name="timestamp" /> + <arg type="u" name="type" /> + <arg type="s" name="text" /> + </signal> + </interface> + <interface name="org.freedesktop.Telepathy.Channel"> + <method name="GetHandle"> + <arg direction="out" type="u" /> + <arg direction="out" type="u" /> + </method> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + <method name="GetChannelType"> + <arg direction="out" type="s" /> + </method> + <signal name="Closed"> + </signal> + <method name="Close"> + </method> + </interface> +</node> diff --git a/generate/xml-modified/idle-muc-channel.xml b/generate/xml-modified/idle-muc-channel.xml new file mode 100644 index 0000000..ad74e83 --- /dev/null +++ b/generate/xml-modified/idle-muc-channel.xml @@ -0,0 +1,135 @@ +<?xml version="1.0" ?> +<node name="/IdleMUCChannel"> + <interface name="org.freedesktop.Telepathy.Channel"> + <method name="Close"> + </method> + <signal name="Closed"> + </signal> + <method name="GetChannelType"> + <arg direction="out" type="s" /> + </method> + <method name="GetHandle"> + <arg direction="out" type="u" /> + <arg direction="out" type="u" /> + </method> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Interface.Group"> + <method name="AddMembers"> + <arg direction="in" name="contacts" type="au" /> + <arg direction="in" name="message" type="s" /> + </method> + <method name="GetAllMembers"> + <arg direction="out" type="au" /> + <arg direction="out" type="au" /> + <arg direction="out" type="au" /> + </method> + <method name="GetGroupFlags"> + <arg direction="out" type="u" /> + </method> + <method name="GetHandleOwners"> + <arg direction="in" name="handles" type="au" /> + <arg direction="out" type="au" /> + </method> + <method name="GetLocalPendingMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetRemotePendingMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetSelfHandle"> + <arg direction="out" type="u" /> + </method> + <signal name="GroupFlagsChanged"> + <arg name="added" type="u" /> + <arg name="removed" type="u" /> + </signal> + <signal name="MembersChanged"> + <arg name="message" type="s" /> + <arg name="added" type="au" /> + <arg name="removed" type="au" /> + <arg name="local_pending" type="au" /> + <arg name="remote_pending" type="au" /> + <arg name="actor" type="u" /> + <arg name="reason" type="u" /> + </signal> + <method name="RemoveMembers"> + <arg direction="in" name="contacts" type="au" /> + <arg direction="in" name="message" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Interface.Password"> + <method name="GetPasswordFlags"> + <arg direction="out" type="u" /> + </method> + <signal name="PasswordFlagsChanged"> + <arg name="added" type="u" /> + <arg name="removed" type="u" /> + </signal> + <method name="ProvidePassword"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg direction="in" name="password" type="s" /> + <arg direction="out" type="b" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Type.Text"> + <method name="AcknowledgePendingMessages"> + <arg direction="in" name="id" type="au" /> + </method> + <method name="GetMessageTypes"> + <arg direction="out" type="au" /> + </method> + <method name="ListPendingMessages"> + <arg direction="in" name="clear" type="b" /> + <arg direction="out" type="a(uuuuus)" /> + </method> + <signal name="LostMessage"> + </signal> + <signal name="Received"> + <arg name="id" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="sender" type="u" /> + <arg name="type" type="u" /> + <arg name="flags" type="u" /> + <arg name="text" type="s" /> + </signal> + <method name="Send"> + <arg direction="in" name="type" type="u" /> + <arg direction="in" name="text" type="s" /> + </method> + <signal name="SendError"> + <arg name="error" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + <signal name="Sent"> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + </interface> +<interface name="org.freedesktop.Telepathy.Properties"> + <method name="GetProperties"> + <arg direction="in" name="properties" type="au" /> + <arg direction="out" type="a(uv)" /> + </method> + <method name="ListProperties"> + <arg direction="out" type="a(ussu)" /> + </method> + <signal name="PropertiesChanged"> + <arg name="properties" type="a(uv)" /> + </signal> + <signal name="PropertyFlagsChanged"> + <arg name="properties" type="a(uu)" /> + </signal> + <method name="SetProperties"> + <arg direction="in" name="properties" type="a(uv)" /> + </method> + </interface> +</node> diff --git a/generate/xml-pristine/.git-darcs-dir b/generate/xml-pristine/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/generate/xml-pristine/.git-darcs-dir diff --git a/generate/xml-pristine/idle-connection-manager.xml b/generate/xml-pristine/idle-connection-manager.xml new file mode 100644 index 0000000..9a9ce21 --- /dev/null +++ b/generate/xml-pristine/idle-connection-manager.xml @@ -0,0 +1,27 @@ +<node name="/IdleConnectionManager"> + <interface name="org.freedesktop.DBus.Introspectable"> + <method name="Introspect"> + <arg direction="out" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.ConnectionManager"> + <method name="GetParameters"> + <arg direction="in" name="proto" type="s" /> + <arg direction="out" type="a(susv)" /> + </method> + <method name="ListProtocols"> + <arg direction="out" type="as" /> + </method> + <signal name="NewConnection"> + <arg name="bus_name" type="s" /> + <arg name="object_path" type="o" /> + <arg name="proto" type="s" /> + </signal> + <method name="RequestConnection"> + <arg direction="in" name="proto" type="s" /> + <arg direction="in" name="parameters" type="a{sv}" /> + <arg direction="out" type="s" /> + <arg direction="out" type="o" /> + </method> + </interface> +</node>
\ No newline at end of file diff --git a/generate/xml-pristine/idle-connection.xml b/generate/xml-pristine/idle-connection.xml new file mode 100644 index 0000000..aaf4624 --- /dev/null +++ b/generate/xml-pristine/idle-connection.xml @@ -0,0 +1,113 @@ +<node name="/IdleConnection"> + <interface name="org.freedesktop.DBus.Introspectable"> + <method name="Introspect"> + <arg direction="out" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Connection"> + <method name="Connect"> + </method> + <method name="Disconnect"> + </method> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + <method name="GetProtocol"> + <arg direction="out" type="s" /> + </method> + <method name="GetSelfHandle"> + <arg direction="out" type="u" /> + </method> + <method name="GetStatus"> + <arg direction="out" type="u" /> + </method> + <method name="HoldHandles"> + <arg direction="in" name="handle_type" type="u" /> + <arg direction="in" name="handles" type="au" /> + </method> + <method name="InspectHandles"> + <arg direction="in" name="handle_type" type="u" /> + <arg direction="in" name="handles" type="au" /> + <arg direction="out" type="as" /> + </method> + <method name="ListChannels"> + <arg direction="out" type="a(osuu)" /> + </method> + <signal name="NewChannel"> + <arg name="object_path" type="o" /> + <arg name="channel_type" type="s" /> + <arg name="handle_type" type="u" /> + <arg name="handle" type="u" /> + <arg name="suppress_handler" type="b" /> + </signal> + <method name="ReleaseHandles"> + <arg direction="in" name="handle_type" type="u" /> + <arg direction="in" name="handles" type="au" /> + </method> + <method name="RequestChannel"> + <arg direction="in" name="type" type="s" /> + <arg direction="in" name="handle_type" type="u" /> + <arg direction="in" name="handle" type="u" /> + <arg direction="in" name="suppress_handler" type="b" /> + <arg direction="out" type="o" /> + </method> + <method name="RequestHandles"> + <arg direction="in" name="handle_type" type="u" /> + <arg direction="in" name="names" type="as" /> + <arg direction="out" type="au" /> + </method> + <signal name="StatusChanged"> + <arg name="status" type="u" /> + <arg name="reason" type="u" /> + </signal> + </interface> + <interface name="org.freedesktop.Telepathy.Connection.Interface.Capabilities"> + <method name="AdvertiseCapabilities"> + <arg direction="in" name="add" type="as" /> + <arg direction="in" name="remove" type="as" /> + <arg direction="out" type="as" /> + </method> + <signal name="CapabilitiesChanged"> + <arg name="caps" type="a(usuu)" /> + </signal> + <method name="GetCapabilities"> + <arg direction="in" name="handles" type="au" /> + <arg direction="out" type="a(usu)" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Connection.Interface.Presence"> + <method name="AddStatus"> + <arg direction="in" name="status" type="s" /> + <arg direction="in" name="parms" type="a{sv}" /> + </method> + <method name="ClearStatus"> + </method> + <method name="GetStatuses"> + <arg direction="out" type="a{s(ubba{ss})}" /> + </method> + <signal name="PresenceUpdate"> + <arg name="presence" type="a{u(ua{sa{sv}})}" /> + </signal> + <method name="RemoveStatus"> + <arg direction="in" name="status" type="s" /> + </method> + <method name="RequestPresence"> + <arg direction="in" name="contacts" type="au" /> + </method> + <method name="SetLastActivityTime"> + <arg direction="in" name="time" type="u" /> + </method> + <method name="SetStatus"> + <arg direction="in" name="statuses" type="a{sa{sv}}" /> + </method> + </interface> +<interface name="org.freedesktop.Telepathy.Connection.Interface.Renaming"> + <signal name="Renamed"> + <arg name="original" type="u" /> + <arg name="new" type="u" /> + </signal> + <method name="RequestRename"> + <arg direction="in" name="name" type="s" /> + </method> + </interface> + </node>
\ No newline at end of file diff --git a/generate/xml-pristine/idle-im-channel.xml b/generate/xml-pristine/idle-im-channel.xml new file mode 100644 index 0000000..a5182e7 --- /dev/null +++ b/generate/xml-pristine/idle-im-channel.xml @@ -0,0 +1,60 @@ +<node name="/IdleIMChannel"> + <interface name="org.freedesktop.DBus.Introspectable"> + <method name="Introspect"> + <arg direction="out" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel"> + <method name="Close"> + </method> + <signal name="Closed"> + </signal> + <method name="GetChannelType"> + <arg direction="out" type="s" /> + </method> + <method name="GetHandle"> + <arg direction="out" type="u" /> + <arg direction="out" type="u" /> + </method> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + </interface> +<interface name="org.freedesktop.Telepathy.Channel.Type.Text"> + <method name="AcknowledgePendingMessages"> + <arg direction="in" name="ids" type="au" /> + </method> + <method name="GetMessageTypes"> + <arg direction="out" type="au" /> + </method> + <method name="ListPendingMessages"> + <arg direction="in" name="clear" type="b" /> + <arg direction="out" type="a(uuuus)" /> + </method> + <signal name="LostMessage"> + </signal> + <signal name="Received"> + <arg name="id" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="sender" type="u" /> + <arg name="type" type="u" /> + <arg name="flags" type="u" /> + <arg name="text" type="s" /> + </signal> + <method name="Send"> + <arg direction="in" name="type" type="u" /> + <arg direction="in" name="text" type="s" /> + </method> + <signal name="SendError"> + <arg name="error" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + <signal name="Sent"> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + </interface> + </node>
\ No newline at end of file diff --git a/generate/xml-pristine/idle-muc-channel.xml b/generate/xml-pristine/idle-muc-channel.xml new file mode 100644 index 0000000..6749978 --- /dev/null +++ b/generate/xml-pristine/idle-muc-channel.xml @@ -0,0 +1,138 @@ +<node name="/IdleMUCChannel"> + <interface name="org.freedesktop.DBus.Introspectable"> + <method name="Introspect"> + <arg direction="out" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel"> + <method name="Close"> + </method> + <signal name="Closed"> + </signal> + <method name="GetChannelType"> + <arg direction="out" type="s" /> + </method> + <method name="GetHandle"> + <arg direction="out" type="u" /> + <arg direction="out" type="u" /> + </method> + <method name="GetInterfaces"> + <arg direction="out" type="as" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Interface.Group"> + <method name="AddMembers"> + <arg direction="in" name="contacts" type="au" /> + <arg direction="in" name="message" type="s" /> + </method> + <method name="GetAllMembers"> + <arg direction="out" type="au" /> + <arg direction="out" type="au" /> + <arg direction="out" type="au" /> + </method> + <method name="GetGroupFlags"> + <arg direction="out" type="u" /> + </method> + <method name="GetHandleOwners"> + <arg direction="in" name="handles" type="au" /> + <arg direction="out" type="au" /> + </method> + <method name="GetLocalPendingMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetRemotePendingMembers"> + <arg direction="out" type="au" /> + </method> + <method name="GetSelfHandle"> + <arg direction="out" type="u" /> + </method> + <signal name="GroupFlagsChanged"> + <arg name="added" type="u" /> + <arg name="removed" type="u" /> + </signal> + <signal name="MembersChanged"> + <arg name="message" type="s" /> + <arg name="added" type="au" /> + <arg name="removed" type="au" /> + <arg name="local_pending" type="au" /> + <arg name="remote_pending" type="au" /> + <arg name="actor" type="u" /> + <arg name="reason" type="u" /> + </signal> + <method name="RemoveMembers"> + <arg direction="in" name="contacts" type="au" /> + <arg direction="in" name="message" type="s" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Interface.Password"> + <method name="GetPasswordFlags"> + <arg direction="out" type="u" /> + </method> + <signal name="PasswordFlagsChanged"> + <arg name="added" type="u" /> + <arg name="removed" type="u" /> + </signal> + <method name="ProvidePassword"> + <arg direction="in" name="password" type="s" /> + <arg direction="out" type="b" /> + </method> + </interface> + <interface name="org.freedesktop.Telepathy.Channel.Type.Text"> + <method name="AcknowledgePendingMessages"> + <arg direction="in" name="ids" type="au" /> + </method> + <method name="GetMessageTypes"> + <arg direction="out" type="au" /> + </method> + <method name="ListPendingMessages"> + <arg direction="in" name="clear" type="b" /> + <arg direction="out" type="a(uuuus)" /> + </method> + <signal name="LostMessage"> + </signal> + <signal name="Received"> + <arg name="id" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="sender" type="u" /> + <arg name="type" type="u" /> + <arg name="flags" type="u" /> + <arg name="text" type="s" /> + </signal> + <method name="Send"> + <arg direction="in" name="type" type="u" /> + <arg direction="in" name="text" type="s" /> + </method> + <signal name="SendError"> + <arg name="error" type="u" /> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + <signal name="Sent"> + <arg name="timestamp" type="u" /> + <arg name="type" type="u" /> + <arg name="text" type="s" /> + </signal> + </interface> +<interface name="org.freedesktop.Telepathy.Properties"> + <method name="GetProperties"> + <arg direction="in" name="properties" type="au" /> + <arg direction="out" type="a(uv)" /> + </method> + <method name="ListProperties"> + <arg direction="out" type="a(ussu)" /> + </method> + <signal name="PropertiesChanged"> + <arg name="properties" type="a(uv)" /> + </signal> + <signal name="PropertyFlagsChanged"> + <arg name="properties" type="a(uu)" /> + </signal> + <method name="SetProperties"> + <arg direction="in" name="properties" type="a(uv)" /> + </method> + </interface> + </node>
\ No newline at end of file diff --git a/m4/.git-darcs-dir b/m4/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/m4/.git-darcs-dir diff --git a/m4/Makefile.am b/m4/Makefile.am new file mode 100644 index 0000000..259e3c3 --- /dev/null +++ b/m4/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = \ +as-compiler-flag.m4 \ +as-version.m4 \ +as-ac-expand.m4 diff --git a/m4/as-ac-expand.m4 b/m4/as-ac-expand.m4 new file mode 100644 index 0000000..0c71173 --- /dev/null +++ b/m4/as-ac-expand.m4 @@ -0,0 +1,40 @@ +dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) +dnl +dnl example +dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) +dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local + +AC_DEFUN([AS_AC_EXPAND], +[ + EXP_VAR=[$1] + FROM_VAR=[$2] + + dnl first expand prefix and exec_prefix if necessary + prefix_save=$prefix + exec_prefix_save=$exec_prefix + + dnl if no prefix given, then use /usr/local, the default prefix + if test "x$prefix" = "xNONE"; then + prefix=$ac_default_prefix + fi + dnl if no exec_prefix given, then use prefix + if test "x$exec_prefix" = "xNONE"; then + exec_prefix=$prefix + fi + + full_var="$FROM_VAR" + dnl loop until it doesn't change anymore + while true; do + new_full_var="`eval echo $full_var`" + if test "x$new_full_var"="x$full_var"; then break; fi + full_var=$new_full_var + done + + dnl clean up + full_var=$new_full_var + AC_SUBST([$1], "$full_var") + + dnl restore prefix and exec_prefix + prefix=$prefix_save + exec_prefix=$exec_prefix_save +]) diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4 new file mode 100644 index 0000000..605708a --- /dev/null +++ b/m4/as-compiler-flag.m4 @@ -0,0 +1,33 @@ +dnl as-compiler-flag.m4 0.1.0 + +dnl autostars m4 macro for detection of compiler flags + +dnl David Schleef <ds@schleef.org> + +dnl $Id: as-compiler-flag.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $ + +dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED]) +dnl Tries to compile with the given CFLAGS. +dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags, +dnl and ACTION-IF-NOT-ACCEPTED otherwise. + +AC_DEFUN([AS_COMPILER_FLAG], +[ + AC_MSG_CHECKING([to see if compiler understands $1]) + + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $1" + + AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no]) + CFLAGS="$save_CFLAGS" + + if test "X$flag_ok" = Xyes ; then + $2 + true + else + $3 + true + fi + AC_MSG_RESULT([$flag_ok]) +]) + diff --git a/m4/as-version.m4 b/m4/as-version.m4 new file mode 100644 index 0000000..defc887 --- /dev/null +++ b/m4/as-version.m4 @@ -0,0 +1,66 @@ +dnl as-version.m4 0.1.0 + +dnl autostars m4 macro for versioning + +dnl Thomas Vander Stichele <thomas at apestaart dot org> + +dnl $Id: as-version.m4,v 1.1 2005/06/18 18:02:46 burgerman Exp $ + +dnl AS_VERSION(PACKAGE, PREFIX, MAJOR, MINOR, MICRO, NANO, +dnl ACTION-IF-NO-NANO, [ACTION-IF-NANO]) + +dnl example +dnl AS_VERSION(gstreamer, GST_VERSION, 0, 3, 2,) +dnl for a 0.3.2 release version + +dnl this macro +dnl - defines [$PREFIX]_MAJOR, MINOR and MICRO +dnl - if NANO is empty, then we're in release mode, else in cvs/dev mode +dnl - defines [$PREFIX], VERSION, and [$PREFIX]_RELEASE +dnl - executes the relevant action +dnl - AC_SUBST's PACKAGE, VERSION, [$PREFIX] and [$PREFIX]_RELEASE +dnl as well as the little ones +dnl - doesn't call AM_INIT_AUTOMAKE anymore because it prevents +dnl maintainer mode from running ok +dnl +dnl don't forget to put #undef [$2] and [$2]_RELEASE in acconfig.h +dnl if you use acconfig.h + +AC_DEFUN([AS_VERSION], +[ + PACKAGE=[$1] + [$2]_MAJOR=[$3] + [$2]_MINOR=[$4] + [$2]_MICRO=[$5] + NANO=[$6] + [$2]_NANO=$NANO + if test "x$NANO" = "x" || test "x$NANO" = "x0"; + then + AC_MSG_NOTICE(configuring [$1] for release) + VERSION=[$3].[$4].[$5] + [$2]_RELEASE=1 + dnl execute action + ifelse([$7], , :, [$7]) + else + AC_MSG_NOTICE(configuring [$1] for development with nano $NANO) + VERSION=[$3].[$4].[$5].$NANO + [$2]_RELEASE=0.`date +%Y%m%d.%H%M%S` + dnl execute action + ifelse([$8], , :, [$8]) + fi + + [$2]=$VERSION + AC_DEFINE_UNQUOTED([$2], "$[$2]", [Define the version]) + AC_SUBST([$2]) + AC_DEFINE_UNQUOTED([$2]_RELEASE, "$[$2]_RELEASE", [Define the release version]) + AC_SUBST([$2]_RELEASE) + + AC_SUBST([$2]_MAJOR) + AC_SUBST([$2]_MINOR) + AC_SUBST([$2]_MICRO) + AC_SUBST([$2]_NANO) + AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Define the package name]) + AC_SUBST(PACKAGE) + AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Define the version]) + AC_SUBST(VERSION) +]) diff --git a/src/.git-darcs-dir b/src/.git-darcs-dir new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/.git-darcs-dir diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..7552090 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,112 @@ +BUILT_SOURCES = \ + idle-connection-manager-glue.h \ + idle-connection-manager-signals-marshal.h \ + idle-connection-manager-signals-marshal.c \ + idle-connection-glue.h \ + idle-connection-signals-marshal.h \ + idle-connection-signals-marshal.c \ + idle-im-channel-glue.h \ + idle-im-channel-signals-marshal.h \ + idle-im-channel-signals-marshal.c \ + idle-muc-channel-glue.h \ + idle-muc-channel-signals-marshal.h \ + idle-muc-channel-signals-marshal.c \ + telepathy-errors-enumtypes.h \ + telepathy-errors-enumtypes.c \ + idle-server-connection-iface-signals-marshal.h \ + idle-server-connection-iface-signals-marshal.c \ + config.h + +# correctly clean the generated source files +CLEANFILES = $(BUILT_SOURCES) + +bin_PROGRAMS=telepathy-idle + +CORE_SOURCES = \ + idle-connection-manager.h \ + idle-connection.h \ + idle-im-channel.h \ + idle-muc-channel.h \ + idle-handles.h \ + idle-handle-set.h \ + idle-handles-private.h \ + gintset.h \ + gheap.h \ + telepathy-errors.h \ + telepathy-helpers.h \ + telepathy-constants.h \ + telepathy-interfaces.h \ + idle-server-connection-iface.h \ + idle-server-connection.h \ + idle-server-connection-util.h \ + idle-dns-resolver.h \ + idle-ssl-server-connection.h \ + idle-connection-manager.c \ + idle-connection.c \ + idle-im-channel.c \ + idle-muc-channel.c \ + idle-handles.c \ + idle-handle-set.c \ + gintset.c \ + gheap.c \ + telepathy-errors.c \ + telepathy-helpers.c \ + idle-server-connection-iface.c \ + idle-server-connection.c \ + idle-server-connection-util.c \ + idle-dns-resolver.c \ + idle-ssl-server-connection.c \ + idle-version.h \ + $(BUILT_SOURCES) + +libidle_convenience_la_SOURCES = \ + $(CORE_SOURCES) + +telepathy_idle_SOURCES = \ + idle.c \ + idle.h + +telepathy_idle_LDADD = libidle-convenience.la + +noinst_LTLIBRARIES = libidle-convenience.la + +AM_CFLAGS = $(ERROR_CFLAGS) @DBUS_CFLAGS@ @GLIB_CFLAGS@ @SOFIA_CFLAGS@ @OPENSSL_CFLAGS@ +AM_LDFLAGS = @DBUS_LIBS@ @GLIB_LIBS@ @SOFIA_LIBS@ @OPENSSL_LIBS@ + +# rule to generate the binding headers +%-glue.h: ../generate/xml-modified/%.xml Makefile + dbus-binding-tool --mode=glib-server --output=$@ --prefix=$(subst -,_,$*) $< + +%-signals-marshal.h: %-signals-marshal.list Makefile + glib-genmarshal --header --prefix=$(subst -,_,$*)_marshal $< > $*-signals-marshal.h + +%-signals-marshal.c: %-signals-marshal.list Makefile + glib-genmarshal --body --prefix=$(subst -,_,$*)_marshal $< > $*-signals-marshal.c + +%-marshal.h: %-marshal.list Makefile + glib-genmarshal --header --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.h + +%-marshal.c: %-marshal.list Makefile + glib-genmarshal --body --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.c + + +# rules for making the glib enum objects +%-enumtypes.h: %.h Makefile + glib-mkenums \ + --fhead "#ifndef __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n#define __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ + --fprod "/* enumerations from \"@filename@\" */\n" \ + --vhead "GType @enum_name@_get_type (void);\n#define $(shell echo $* | tr [:lower:]- [:upper:]_ | sed 's/_.*//')_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ + --ftail "G_END_DECLS\n\n#endif /* __$(shell echo $* | tr [:lower:]- [:upper:]_)_ENUM_TYPES_H__ */" \ + $< > $@ + +%-enumtypes.c: %.h Makefile + glib-mkenums \ + --fhead "#include <$*.h>" \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@VALUENAME@\" }," \ + --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ + $< > $@ + +config.h: + echo '#define TELEPATHY_IDLE_VERSION "'@TELEPATHY_IDLE_VERSION@'"' > config.h diff --git a/src/gheap.c b/src/gheap.c new file mode 100644 index 0000000..917357e --- /dev/null +++ b/src/gheap.c @@ -0,0 +1,142 @@ +/* + * Header file for GHeap + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 "gheap.h" + +#define DEFAULT_SIZE 64 + +struct _GHeap +{ + GPtrArray *data; + GCompareFunc comparator; +}; + +GHeap *g_heap_new (GCompareFunc comparator) +{ + GHeap *ret = g_slice_new(GHeap); + g_assert (comparator != NULL); + + ret->data = g_ptr_array_sized_new (DEFAULT_SIZE); + ret->comparator = comparator; + + return ret; +} + +void g_heap_destroy (GHeap *heap) +{ + g_return_if_fail (heap != NULL); + + g_ptr_array_free (heap->data, TRUE); + g_slice_free (GHeap, heap); +} + +void g_heap_clear (GHeap *heap) +{ + g_return_if_fail (heap != NULL); + + g_ptr_array_free (heap->data, TRUE); + heap->data = g_ptr_array_sized_new (DEFAULT_SIZE); +} + +#define HEAP_INDEX(heap, index) (g_ptr_array_index ((heap)->data, (index)-1)) + +void g_heap_add (GHeap *heap, gpointer element) +{ + guint m; + + g_return_if_fail (heap != NULL); + + g_ptr_array_add (heap->data, element); + m = heap->data->len; + while (m != 1) + { + gpointer parent = HEAP_INDEX (heap, m/2); + + if (heap->comparator (element, parent) == -1) + { + HEAP_INDEX (heap, m/2) = element; + HEAP_INDEX (heap, m) = parent; + m /= 2; + } + else + break; + } +} + +gpointer g_heap_peek_first (GHeap *heap) +{ + g_return_val_if_fail (heap != NULL, NULL); + + if (heap->data->len > 0) + return HEAP_INDEX (heap, 1); + else + return NULL; +} + +gpointer g_heap_extract_first (GHeap *heap) +{ + gpointer ret; + + g_return_val_if_fail (heap != NULL, NULL); + + if (heap->data->len > 0) + { + guint m = heap->data->len; + guint i = 1, j; + ret = HEAP_INDEX (heap, 1); + + HEAP_INDEX (heap, 1) = HEAP_INDEX (heap, m); + + while (i*2 <= m) + { + /* select the child which is supposed to come FIRST */ + if ((i*2+1 <= m) + && (heap->comparator (HEAP_INDEX (heap, i*2), HEAP_INDEX (heap, i*2+1)) == 1)) + j = i*2+1; + else + j = i*2; + + if (heap->comparator (HEAP_INDEX (heap, i), HEAP_INDEX (heap, j)) == 1) + { + gpointer tmp = HEAP_INDEX (heap, i); + HEAP_INDEX (heap, i) = HEAP_INDEX (heap, j); + HEAP_INDEX (heap, j) = tmp; + i = j; + } + else + break; + } + + g_ptr_array_remove_index (heap->data, m-1); + } + else + ret = NULL; + + return ret; +} + +guint g_heap_size (GHeap *heap) +{ + g_return_val_if_fail (heap != NULL, 0); + + return heap->data->len; +} + diff --git a/src/gheap.h b/src/gheap.h new file mode 100644 index 0000000..68bf4b9 --- /dev/null +++ b/src/gheap.h @@ -0,0 +1,38 @@ +/* + * Header file for GHeap + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __G_HEAP_H__ +#define __G_HEAP_H__ + +#include <glib.h> + +typedef struct _GHeap GHeap; + +GHeap *g_heap_new (GCompareFunc comparator); +void g_heap_destroy (GHeap *); +void g_heap_clear (GHeap *); + +void g_heap_add (GHeap *heap, gpointer element); +gpointer g_heap_peek_first (GHeap *heap); +gpointer g_heap_extract_first (GHeap *heap); + +guint g_heap_size (GHeap *heap); + +#endif diff --git a/src/gintset.c b/src/gintset.c new file mode 100644 index 0000000..7076782 --- /dev/null +++ b/src/gintset.c @@ -0,0 +1,353 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <string.h> +#include <glib.h> +#include "gintset.h" + +#define DEFAULT_SIZE 32 +#define DEFAULT_INCREMENT 8 +#define DEFAULT_INCREMENT_LOG2 3 + +struct _GIntSet +{ + guint32 *bits; + guint size; +}; + +static GIntSet * +_g_intset_new_with_size (guint size) +{ + GIntSet *set = g_slice_new(GIntSet); + set->bits = g_new0(guint32, size); + set->size = size; + return set; +} + +GIntSet * +g_intset_new () +{ + return _g_intset_new_with_size (DEFAULT_SIZE); +} + +/** + * g_intset_destroy: + * @set: set + * + * delete the set + */ +void +g_intset_destroy (GIntSet *set) +{ + g_free (set->bits); + g_slice_free (GIntSet, set); +} + +/** + * g_intset_add: + * @set: set + * @element: integer to add + * + * Add an integer into a GIntSet + */ +void +g_intset_add (GIntSet *set, guint element) +{ + guint offset = element>>5; + guint newsize; + if (offset >= set->size) + { + newsize = ((offset>>DEFAULT_INCREMENT_LOG2) +1 ) << DEFAULT_INCREMENT_LOG2; + set->bits = g_renew(guint32, set->bits, newsize); + memset (set->bits + set->size, 0, sizeof(guint32) * (newsize - set->size)); + set->size = newsize; + g_assert(offset<newsize); + } + set->bits[offset] = set->bits[offset] | (1<<(element & 0x1f)); +} + +/** + * g_intset_remove: + * @set: set + * @element: integer to add + * + * Remove an integer from a GIntSet + * Returns: TRUE if element was in set + */ +gboolean +g_intset_remove (GIntSet *set, guint element) +{ + guint offset = element >>5; + guint mask = 1 << (element & 0x1f); + if (offset >= set->size) + return FALSE; + else if (! (set->bits[offset] & mask)) + return FALSE; + else + { + set->bits[offset] &= ~mask; + return TRUE; + } +} + +/** + * g_intset_is_member: + * @set: set + * @element: integer to test + * + * Tests if @element is a member of @set + * Returns: TRUE if element was in set + */ +gboolean +g_intset_is_member (const GIntSet *set, guint element) +{ + guint offset = element >>5; + if (offset >= set->size) + return FALSE; + else + return (set->bits[offset] & (1 << (element & 0x1f))); +} + +/** + * g_intset_foreach: + * @set: set + * @func: @GIntFunc to use to iterate the set + * @userdata: user data to pass to each call of @func + * + * Iterates every member of the set calling @func + */ + +void +g_intset_foreach (GIntSet *set, GIntFunc func, gpointer userdata) +{ + guint i, j; + for (i=0; i<set->size; i++) + { + if (set->bits[i]) + { + for (j=0; j<32; j++) + { + if (set->bits[i] & 1<<j) + func(i*32 +j, userdata); + } + } + } +} + + +static void +addint(guint32 i, gpointer data) +{ + GArray * array = (GArray *) data; + g_array_append_val (array, i); +} + +/** + * g_intset_to_array: + * @set: set to convert + * Convert a gintset to an array, allocates the array for you, hence you + * must free it after use. + */ +GArray * +g_intset_to_array (GIntSet *set) +{ + GArray *array; + + array = g_array_new (FALSE, TRUE, sizeof (guint32)); + + g_intset_foreach(set, addint, array); + return array; +} + +int +g_intset_size(const GIntSet *set) +{ + int i,count=0; + guint32 n; + + for (i=0; i< set->size ; i++) + { + n = set->bits[i]; + n = n - ((n >> 1) & 033333333333) - ((n >> 2) & 011111111111); + count += ((n + (n >> 3)) & 030707070707) % 63; + } + return count; +} + +gboolean +g_intset_is_equal (const GIntSet *left, const GIntSet *right) +{ + const GIntSet *large, *small; + int i; + + g_return_val_if_fail (left != NULL, FALSE); + g_return_val_if_fail (right != NULL, FALSE); + + if (left->size > right->size) + { + large = left; + small = right; + } + else + { + large = right; + small = left; + } + + for (i = 0; i < small->size; i++) + { + if (large->bits[i] != small->bits[i]) + return FALSE; + } + + for (i = small->size; i < large->size; i++) + { + if (large->bits[i] != 0) + return FALSE; + } + + return TRUE; +} + +GIntSet * +g_intset_copy (const GIntSet *orig) +{ + GIntSet *ret; + + g_return_val_if_fail (orig != NULL, NULL); + + ret = _g_intset_new_with_size (orig->size); + memcpy (ret->bits, orig->bits, (ret->size * sizeof(guint32))); + + return ret; +} + +GIntSet * +g_intset_intersection (const GIntSet *left, const GIntSet *right) +{ + const GIntSet *large, *small; + GIntSet *ret; + int i; + + g_return_val_if_fail (left != NULL, NULL); + g_return_val_if_fail (right != NULL, NULL); + + if (left->size > right->size) + { + large = left; + small = right; + } + else + { + large = right; + small = left; + } + + ret = g_intset_copy (small); + + for (i = 0; i < ret->size; i++) + { + ret->bits[i] &= large->bits[i]; + } + + return ret; +} + +GIntSet * +g_intset_union (const GIntSet *left, const GIntSet *right) +{ + const GIntSet *large, *small; + GIntSet *ret; + int i; + + g_return_val_if_fail (left != NULL, NULL); + g_return_val_if_fail (right != NULL, NULL); + + if (left->size > right->size) + { + large = left; + small = right; + } + else + { + large = right; + small = left; + } + + ret = g_intset_copy (large); + + for (i = 0; i < small->size; i++) + { + ret->bits[i] |= small->bits[i]; + } + + return ret; +} + +GIntSet * +g_intset_difference (const GIntSet *left, const GIntSet *right) +{ + GIntSet *ret; + int i; + + g_return_val_if_fail (left != NULL, NULL); + g_return_val_if_fail (right != NULL, NULL); + + ret = g_intset_copy (left); + + for (i = 0; i < MIN(right->size, left->size); i++) + { + ret->bits[i] &= ~right->bits[i]; + } + + return ret; +} + +GIntSet * +g_intset_symmetric_difference (const GIntSet *left, const GIntSet *right) +{ + const GIntSet *large, *small; + GIntSet *ret; + int i; + + g_return_val_if_fail (left != NULL, NULL); + g_return_val_if_fail (right != NULL, NULL); + + if (left->size > right->size) + { + large = left; + small = right; + } + else + { + large = right; + small = left; + } + + ret = g_intset_copy (large); + + for (i = 0; i < small->size; i++) + { + ret->bits[i] ^= small->bits[i]; + } + + return ret; +} + diff --git a/src/gintset.h b/src/gintset.h new file mode 100644 index 0000000..c83c7ff --- /dev/null +++ b/src/gintset.h @@ -0,0 +1,49 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __G_INTSET_H__ +#define __G_INTSET_H__ + +#include <glib-object.h> + +typedef struct _GIntSet GIntSet; +typedef void (*GIntFunc)(guint i, gpointer userdata); + +GIntSet * g_intset_new (); +void g_intset_destroy (GIntSet *); + +void g_intset_add (GIntSet *set, guint element); +gboolean g_intset_remove (GIntSet *set, guint element); +gboolean g_intset_is_member (const GIntSet *set, guint element); + +void g_intset_foreach (GIntSet *set, GIntFunc func, gpointer userdata); +GArray* g_intset_to_array (GIntSet *set); + +int g_intset_size(const GIntSet *set); + +gboolean g_intset_is_equal (const GIntSet *left, const GIntSet *right); + +GIntSet *g_intset_copy (const GIntSet *orig); +GIntSet *g_intset_intersection (const GIntSet *left, const GIntSet *right); +GIntSet *g_intset_union (const GIntSet *left, const GIntSet *right); +GIntSet *g_intset_difference (const GIntSet *left, const GIntSet *right); +GIntSet *g_intset_symmetric_difference (const GIntSet *left, const GIntSet *right); + +#endif /*__G_INTSET_H__*/ diff --git a/src/idle-connection-manager-signals-marshal.list b/src/idle-connection-manager-signals-marshal.list new file mode 100644 index 0000000..41e4027 --- /dev/null +++ b/src/idle-connection-manager-signals-marshal.list @@ -0,0 +1 @@ +VOID:STRING,STRING,STRING diff --git a/src/idle-connection-manager.c b/src/idle-connection-manager.c new file mode 100644 index 0000000..7a1575b --- /dev/null +++ b/src/idle-connection-manager.c @@ -0,0 +1,559 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <dbus/dbus-glib.h> +#include <dbus/dbus-protocol.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "idle-connection.h" +#include "telepathy-constants.h" +#include "telepathy-errors.h" +#include "telepathy-helpers.h" + +#include "idle-connection-manager.h" +#include "idle-connection-manager-glue.h" +#include "idle-connection-manager-signals-marshal.h" + +#define BUS_NAME "org.freedesktop.Telepathy.ConnectionManager.idle" +#define OBJECT_PATH "/org/freedesktop/Telepathy/ConnectionManager/idle" + +G_DEFINE_TYPE(IdleConnectionManager, idle_connection_manager, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + NEW_CONNECTION, + NO_MORE_CONNECTIONS, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleConnectionManagerPrivate IdleConnectionManagerPrivate; + +struct _IdleConnectionManagerPrivate +{ + gboolean dispose_has_run; + GHashTable *connections; +}; + +#define IDLE_CONNECTION_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerPrivate)) + +static void +idle_connection_manager_init (IdleConnectionManager *obj) +{ + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (obj); + + priv->connections = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void idle_connection_manager_dispose (GObject *object); +static void idle_connection_manager_finalize (GObject *object); + +static void +idle_connection_manager_class_init (IdleConnectionManagerClass *idle_connection_manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_connection_manager_class); + + g_type_class_add_private (idle_connection_manager_class, sizeof (IdleConnectionManagerPrivate)); + + object_class->dispose = idle_connection_manager_dispose; + object_class->finalize = idle_connection_manager_finalize; + + signals[NEW_CONNECTION] = + g_signal_new ("new-connection", + G_OBJECT_CLASS_TYPE (idle_connection_manager_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_manager_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, G_TYPE_STRING, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING); + + signals[NO_MORE_CONNECTIONS] = + g_signal_new ("no-more-connections", + G_OBJECT_CLASS_TYPE (idle_connection_manager_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_connection_manager_class), &dbus_glib_idle_connection_manager_object_info); +} + +void +idle_connection_manager_dispose (GObject *object) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER (object); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + + if (G_OBJECT_CLASS (idle_connection_manager_parent_class)->dispose) + G_OBJECT_CLASS (idle_connection_manager_parent_class)->dispose (object); +} + +void +idle_connection_manager_finalize (GObject *object) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER (object); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE (self); + + g_hash_table_destroy(priv->connections); + + G_OBJECT_CLASS (idle_connection_manager_parent_class)->finalize (object); +} + +/* private data */ + +typedef struct _IdleParams IdleParams; + +struct _IdleParams +{ + gchar *nickname; + gchar *server; + guint16 port; + gchar *password; + gchar *realname; + gchar *charset; + gchar *quit_message; + gboolean use_ssl; +}; + +typedef struct _IdleParamSpec IdleParamSpec; + +struct _IdleParamSpec +{ + const gchar *name; + const gchar *dtype; + const GType gtype; + guint flags; + const gpointer def; + const gsize offset; +}; + +static const IdleParamSpec irc_params[] = +{ + {"account", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, TP_CONN_MGR_PARAM_FLAG_REQUIRED, NULL, G_STRUCT_OFFSET(IdleParams, nickname)}, + {"server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, TP_CONN_MGR_PARAM_FLAG_REQUIRED, NULL, G_STRUCT_OFFSET(IdleParams, server)}, + {"port", DBUS_TYPE_UINT16_AS_STRING, G_TYPE_UINT, TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER(6667), G_STRUCT_OFFSET(IdleParams, port)}, + {"password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, G_STRUCT_OFFSET(IdleParams, password)}, + {"fullname", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "telepathy-idle user http://telepathy.freedesktop.org", G_STRUCT_OFFSET(IdleParams, realname)}, + {"charset", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "UTF-8", G_STRUCT_OFFSET(IdleParams, charset)}, + {"quit-message",DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, "So long and thanks for all the IRC - telepathy-idle IRC Connection Manager for Telepathy - http://telepathy.freedesktop.org", G_STRUCT_OFFSET(IdleParams, quit_message)}, + {"use-ssl", DBUS_TYPE_BOOLEAN_AS_STRING, G_TYPE_BOOLEAN, TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, GINT_TO_POINTER(FALSE), G_STRUCT_OFFSET(IdleParams, use_ssl)}, + {NULL, NULL, 0, 0, NULL, 0} +}; + +/* private methods */ + +static gboolean get_parameters (const char *proto, const IdleParamSpec **params, GError **error) +{ + if (!strcmp(proto, "irc")) + { + *params = irc_params; + + return TRUE; + } + else + { + g_debug("%s: unknown protocol %s", G_STRFUNC, proto); + + *error = g_error_new (TELEPATHY_ERRORS, NotImplemented, "unknown protocol %s", proto); + + return FALSE; + } +} + +static void set_default_param (const IdleParamSpec *paramspec, IdleParams *params) +{ + switch (paramspec->dtype[0]) + { + case DBUS_TYPE_STRING: + { + *((char **)((void *)params + paramspec->offset)) = g_strdup(paramspec->def); + break; + } + case DBUS_TYPE_UINT16: + { + *((guint16 *)((void *)params + paramspec->offset)) = GPOINTER_TO_INT(paramspec->def); + break; + } + case DBUS_TYPE_BOOLEAN: + { + *((gboolean *)((void *)params + paramspec->offset)) = GPOINTER_TO_INT(paramspec->def); + break; + } + default: + { + g_error("%s: unknown parameter type %s on parameter %s", G_STRFUNC, paramspec->dtype, paramspec->name); + break; + } + } +} + +static gboolean set_param_from_value (const IdleParamSpec *paramspec, GValue *value, IdleParams *params, GError **error) +{ + if (G_VALUE_TYPE (value) != paramspec->gtype) + { + g_debug("%s: expected type %s for parameter %s, got %s", G_STRFUNC, + g_type_name (paramspec->gtype), + paramspec->name, + G_VALUE_TYPE_NAME (value)); + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, + "expected type %s for account parameter %s, got %s", + g_type_name (paramspec->gtype), paramspec->name, + G_VALUE_TYPE_NAME (value)); + return FALSE; + } + + switch (paramspec->dtype[0]) + { + case DBUS_TYPE_STRING: + { + *((char **)((void *)params + paramspec->offset)) = g_value_dup_string (value); + break; + } + case DBUS_TYPE_UINT16: + { + *((guint16 *)((void *)params + paramspec->offset)) = g_value_get_uint (value); + break; + } + case DBUS_TYPE_BOOLEAN: + { + *((gboolean *)((void *)params + paramspec->offset)) = g_value_get_boolean (value); + break; + } + default: + { + g_error("%s: encountered unknown type %s on argument %s", G_STRFUNC, paramspec->dtype, paramspec->name); + } + } + + return TRUE; +} + +static gboolean parse_parameters (const IdleParamSpec *paramspec, GHashTable *provided, IdleParams *params, GError **error) +{ + int params_left; + int i; + + params_left = g_hash_table_size(provided); + + for (i=0; paramspec[i].name; i++) + { + GValue *value; + value = g_hash_table_lookup(provided, paramspec[i].name); + + if (!value) + { + if (paramspec[i].flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED) + { + g_debug("%s: missing REQUIRED param %s", G_STRFUNC, paramspec[i].name); + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, + "missing REQUIRED account parameter %s", + paramspec[i].name); + return FALSE; + } + else + { + g_debug("%s: using default value for param %s", + G_STRFUNC, paramspec[i].name); + set_default_param(&(paramspec[i]), params); + } + } + else + { + if (!set_param_from_value(&(paramspec[i]), value, params, error)) + { + return FALSE; + } + + params_left--; + + if (paramspec[i].gtype == G_TYPE_STRING) + { + g_debug("%s: new value %s for param %s", G_STRFUNC, + *((char **)((void *)params+paramspec[i].offset)), paramspec[i].name); + } + else + { + g_debug("%s: new value %u for param %s", G_STRFUNC, + *((guint *)((void *)params+paramspec[i].offset)), paramspec[i].name); + + } + } + } + + if (params_left) + { + g_debug("%s: %u unknown parameters given", G_STRFUNC, params_left); + return FALSE; + } + + return TRUE; +} + +static void free_params (IdleParams *params) +{ + g_free(params->nickname); + g_free(params->server); + g_free(params->password); + g_free(params->realname); + g_free(params->charset); + g_free(params->quit_message); +} + +static void connection_disconnected_cb(IdleConnection *conn, gpointer data) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER(data); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE(self); + + g_assert(g_hash_table_lookup(priv->connections, conn)); + g_hash_table_remove(priv->connections, conn); + + g_object_unref(conn); + + g_debug("%s: unref'd connection", G_STRFUNC); + + if (!g_hash_table_size(priv->connections)) + { + g_signal_emit(self, signals[NO_MORE_CONNECTIONS], 0); + } +} + +/* public methods */ + +void +_idle_connection_manager_register (IdleConnectionManager *self) +{ + DBusGConnection *bus; + DBusGProxy *bus_proxy; + GError *error = NULL; + guint request_name_result; + + g_assert (IDLE_IS_CONNECTION_MANAGER (self)); + + bus = tp_get_bus (); + bus_proxy = tp_get_bus_proxy (); + + if (!dbus_g_proxy_call (bus_proxy, "RequestName", &error, + G_TYPE_STRING, BUS_NAME, + G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) + g_error ("Failed to request bus name: %s", error->message); + + if (request_name_result == DBUS_REQUEST_NAME_REPLY_EXISTS) + g_error ("Failed to acquire bus name, connection manager already running?"); + + dbus_g_connection_register_g_object (bus, OBJECT_PATH, G_OBJECT (self)); +} + +/* dbus-exported methods */ + +/** + * idle_connection_manager_connect + * + * Implements DBus method Connect + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_request_connection (IdleConnectionManager *obj, + const gchar *proto, + GHashTable *parameters, + gchar **bus_name, + gchar **object_path, + GError **error) +{ + IdleConnectionManager *self = IDLE_CONNECTION_MANAGER(obj); + IdleConnectionManagerPrivate *priv = IDLE_CONNECTION_MANAGER_GET_PRIVATE(self); + IdleConnection *conn; + const IdleParamSpec *paramspec; + IdleParams params = {NULL}; + + g_assert(IDLE_IS_CONNECTION_MANAGER(self)); + + if (!get_parameters(proto, ¶mspec, error)) + { + return FALSE; + } + + if (!parse_parameters(paramspec, parameters, ¶ms, error)) + { + free_params(¶ms); + return FALSE; + } + + conn = g_object_new(IDLE_TYPE_CONNECTION, + "protocol", proto, + "nickname", params.nickname, + "server", params.server, + "port", params.port, + "password", params.password, + "realname", params.realname, + "charset", params.charset, + "quit-message", params.quit_message, + "use-ssl", params.use_ssl, + NULL); + + free_params(¶ms); + + if (!_idle_connection_register(conn, bus_name, object_path, error)) + { + g_debug("%s failed: %s", G_STRFUNC, (*error)->message); + goto lerror; + } + + g_signal_connect(conn, "disconnected", G_CALLBACK(connection_disconnected_cb), self); + g_hash_table_insert(priv->connections, conn, GINT_TO_POINTER(TRUE)); + + g_signal_emit(obj, signals[NEW_CONNECTION], 0, *bus_name, *object_path, proto); + + return TRUE; + +lerror: + if (conn) + { + g_object_unref(G_OBJECT(conn)); + } + + return FALSE; +} + +gboolean idle_connection_manager_get_parameters (IdleConnectionManager *obj, + const gchar *proto, + GPtrArray **ret, + GError **error) +{ + const IdleParamSpec *params = NULL; + const IdleParamSpec *param; + + if (!get_parameters(proto, ¶ms, error)) + { + return FALSE; + } + + *ret = g_ptr_array_new(); + + for (param = params; param->name != NULL; param++) + { + GValue tp_param = {0, }; + GValue *def; + + def = g_new0(GValue, 1); + g_value_init(def, param->gtype); + + g_value_init(&tp_param, dbus_g_type_get_struct ("GValueArray", + G_TYPE_STRING, + G_TYPE_UINT, + G_TYPE_STRING, + G_TYPE_VALUE, + G_TYPE_INVALID)); + + g_value_set_static_boxed(&tp_param, + dbus_g_type_specialized_construct( + dbus_g_type_get_struct ("GValueArray", + G_TYPE_STRING, + G_TYPE_UINT, + G_TYPE_STRING, + G_TYPE_VALUE, + G_TYPE_INVALID))); + + if (param->def) + { + switch (param->gtype) + { + case G_TYPE_STRING: + { + g_value_set_static_string(def, param->def); + } + break; + case G_TYPE_UINT: + { + g_value_set_uint(def, GPOINTER_TO_UINT(param->def)); + } + break; + case G_TYPE_BOOLEAN: + { + g_value_set_boolean(def, GPOINTER_TO_UINT(param->def)); + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + } + + dbus_g_type_struct_set(&tp_param, + 0, param->name, + 1, param->flags, + 2, param->dtype, + 3, def, + G_MAXUINT); + + g_value_unset(def); + g_free(def); + + g_ptr_array_add(*ret, g_value_get_boxed(&tp_param)); + } + + return TRUE; +} + +/** + * idle_connection_manager_list_protocols + * + * Implements DBus method ListProtocols + * on interface org.freedesktop.Telepathy.ConnectionManager + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_manager_list_protocols (IdleConnectionManager *obj, + gchar *** ret, + GError **error) +{ + const char *protocols[] = {"irc", NULL}; + + *ret = g_strdupv((gchar **)(protocols)); + + return TRUE; +} + diff --git a/src/idle-connection-manager.h b/src/idle-connection-manager.h new file mode 100644 index 0000000..f98839f --- /dev/null +++ b/src/idle-connection-manager.h @@ -0,0 +1,64 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_CONNECTION_MANAGER_H__ +#define __IDLE_CONNECTION_MANAGER_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleConnectionManager IdleConnectionManager; +typedef struct _IdleConnectionManagerClass IdleConnectionManagerClass; + +struct _IdleConnectionManagerClass { + GObjectClass parent_class; +}; + +struct _IdleConnectionManager { + GObject parent; +}; + +GType idle_connection_manager_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_CONNECTION_MANAGER \ + (idle_connection_manager_get_type()) +#define IDLE_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManager)) +#define IDLE_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerClass)) +#define IDLE_IS_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_CONNECTION_MANAGER)) +#define IDLE_IS_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_CONNECTION_MANAGER)) +#define IDLE_CONNECTION_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_CONNECTION_MANAGER, IdleConnectionManagerClass)) + +void _idle_connection_manager_register (IdleConnectionManager *self); + +gboolean idle_connection_manager_request_connection (IdleConnectionManager *obj, const gchar * proto, GHashTable * parameters, gchar ** ret, gchar ** ret1, GError **error); +gboolean idle_connection_manager_get_parameters (IdleConnectionManager *obj, const gchar * proto, GPtrArray **ret, GError **error); +gboolean idle_connection_manager_list_protocols (IdleConnectionManager *obj, gchar *** ret, GError **error); + + +G_END_DECLS + +#endif /* #ifndef __IDLE_CONNECTION_MANAGER_H__*/ diff --git a/src/idle-connection-signals-marshal.list b/src/idle-connection-signals-marshal.list new file mode 100644 index 0000000..239c530 --- /dev/null +++ b/src/idle-connection-signals-marshal.list @@ -0,0 +1,4 @@ +VOID:INT,BOXED,BOXED +VOID:STRING,STRING,INT,INT,BOOLEAN +VOID:BOXED +VOID:INT,INT diff --git a/src/idle-connection.c b/src/idle-connection.c new file mode 100644 index 0000000..a27dbda --- /dev/null +++ b/src/idle-connection.c @@ -0,0 +1,5377 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 DBUS_API_SUBJECT_TO_CHANGE + +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <errno.h> + +/* strnlen */ +#define __USE_GNU +#include <string.h> +#include <ctype.h> + +#include <unistd.h> +#include <pthread.h> +#include <sys/poll.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "telepathy-constants.h" +#include "telepathy-errors.h" +#include "telepathy-helpers.h" +#include "telepathy-interfaces.h" + +#include "idle-handles.h" +#include "idle-handle-set.h" +#include "idle-im-channel.h" +#include "idle-muc-channel.h" +#include "gintset.h" + +#include "idle-server-connection.h" +#include "idle-ssl-server-connection.h" +#include "idle-server-connection-iface.h" +#include "idle-server-connection-util.h" + +#include "idle-connection.h" +#include "idle-connection-glue.h" +#include "idle-connection-signals-marshal.h" + +#include "idle-version.h" + +#define BUS_NAME "org.freedesktop.Telepathy.Connection.idle" +#define OBJECT_PATH "/org/freedesktop/Telepathy/Connection/idle" + +/* +#define TP_CAPABILITY_PAIR_TYPE (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID)) +*/ + +G_DEFINE_TYPE(IdleConnection, idle_connection, G_TYPE_OBJECT); + +#define IRC_PRESENCE_SHOW_AVAILABLE "available" +#define IRC_PRESENCE_SHOW_AWAY "away" +#define IRC_PRESENCE_SHOW_OFFLINE "offline" + +/* maximum IRC message payload */ +#define IRC_MSG_MAXLEN 510 + +#define ERROR_IF_NOT_CONNECTED(CONN, PRIV, ERROR) \ + if ((PRIV)->status != TP_CONN_STATUS_CONNECTED) \ + { \ + /* if (((PRIV)->conn.crashed)) socket_conn_close(CONN);*/ \ + g_debug ("%s: rejected request as disconnected", G_STRFUNC); \ + (ERROR) = g_error_new(TELEPATHY_ERRORS, NotAvailable, \ + "Connection is disconnected"); \ + return FALSE; \ + } + +#define ERROR_IF_NOT_CONNECTED_ASYNC(CONN, PRIV, ERROR, CONTEXT) \ + if ((PRIV)->status != TP_CONN_STATUS_CONNECTED) \ + { \ + /*if (((PRIV)->conn.crashed)) socket_conn_close(CONN); */\ + g_debug ("%s: rejected request as disconnected", G_STRFUNC); \ + (ERROR) = g_error_new(TELEPATHY_ERRORS, NotAvailable, \ + "Connection is disconnected"); \ + dbus_g_method_return_error ((CONTEXT), (ERROR)); \ + g_error_free ((ERROR)); \ + return FALSE; \ + } + +struct _IdleContactPresence +{ + IdlePresenceState presence_state; + gchar *status_message; + guint last_activity; +}; + +#define idle_contact_presence_new() (g_slice_new(IdleContactPresence)) +#define idle_contact_presence_new0() (g_slice_new0(IdleContactPresence)) + +typedef struct _IdleStatusInfo IdleStatusInfo; + +struct _IdleStatusInfo +{ + const gchar *name; + TpConnectionPresenceType presence_type; + const gboolean self, exclusive; +}; + +static const IdleStatusInfo idle_statuses[LAST_IDLE_PRESENCE_ENUM] = +{ + {IRC_PRESENCE_SHOW_AVAILABLE, TP_CONN_PRESENCE_TYPE_AVAILABLE, TRUE, TRUE}, + {IRC_PRESENCE_SHOW_AWAY, TP_CONN_PRESENCE_TYPE_AWAY, TRUE, TRUE}, + {IRC_PRESENCE_SHOW_OFFLINE, TP_CONN_PRESENCE_TYPE_OFFLINE, FALSE, TRUE} +}; + +void idle_contact_presence_free(IdleContactPresence *cp) +{ + g_free(cp->status_message); + g_slice_free(IdleContactPresence, cp); +} + +typedef struct +{ + DBusGMethodInvocation *ctx; + gboolean suppress_handler; +} chan_req_data; + +/* signal enum */ +enum +{ +/* CAPABILITIES_CHANGED,*/ + NEW_CHANNEL, + PRESENCE_UPDATE, + STATUS_CHANGED, + DISCONNECTED, + RENAMED, + LAST_SIGNAL_ENUM +}; + +enum +{ + PROP_PROTOCOL = 1, + PROP_NICKNAME, + PROP_SERVER, + PROP_PORT, + PROP_PASSWORD, + PROP_REALNAME, + PROP_CHARSET, + PROP_QUITMESSAGE, + PROP_USE_SSL, + LAST_PROPERTY_ENUM +}; + +static guint signals[LAST_SIGNAL_ENUM] = {0}; + +/* private structure */ +typedef struct _IdleConnectionPrivate IdleConnectionPrivate; + +#if 0 +/* + * socket/stdio network connection + */ +typedef struct _socket_conn socket_conn; + +typedef void (*socket_conn_connected_cb_t)(IdleConnection *conn, gboolean success); +typedef void (*socket_conn_disconnected_cb_t)(IdleConnection *conn, TpConnectionStatusReason reason); +typedef void (*socket_conn_message_cb_t)(IdleConnection *conn, const gchar *msg); + +struct _socket_conn +{ + int fd; + /*FILE *stream;*/ + + /* we need to spawn a thread to play ping-pong with the server + * while it does that it also sends to the server whatever gets in out_queue + */ + + pthread_t thread; + + /* output message queue */ + GAsyncQueue *out_queue; + + /* possible partial message saved from a earlier bunch */ + gchar *msg_store; + + /* if the comm thread should pause the sending of the queue for a while */ + gboolean queue_wait; + + /* if the comm thread should try and exit in a finite time */ + gboolean exit; + + /* if the comm thread has encountered trouble and wants to be cleaned up */ + gboolean crashed; + + /* callback to call after a connect attempt / a disconnect / a incoming message line */ + socket_conn_connected_cb_t connected_cb; + socket_conn_disconnected_cb_t disconnected_cb; + socket_conn_message_cb_t message_cb; +}; +#endif + +struct _IdleConnectionPrivate +{ + /* + * network connection + */ + + IdleServerConnectionIface *conn; + guint sconn_status; + guint sconn_timeout; + + /* + * disconnect reason + */ + + TpConnectionStatusReason disconnect_reason; + + /* telepathy properties */ + char *protocol; + + /* IRC connection properties */ + char *nickname; + char *server; + guint port; + char *password; + char *realname; + char *charset; + char *quit_message; + gboolean use_ssl; + + /* dbus object location */ + char *bus_name; + char *object_path; + + /* info about us in CTCP VERSION format */ + char *ctcp_version_string; + + /* connection status */ + TpConnectionStatus status; + + /* handles */ + IdleHandleStorage *handles; + IdleHandle self_handle; + + /* channel request contexts */ + GHashTable *chan_req_ctxs; + + /* channels (for queries and regular IRC channels respectively) */ + GHashTable *im_channels; + GHashTable *muc_channels; + + /* client handle set lists */ + GData *cl_contact_handle_sets; + GData *cl_room_handle_sets; + + /* Set of handles whose presence status we should poll and associated polling timer */ + GIntSet *polled_presences; + guint presence_polling_timer_id; + + /* presence query queue, unload timer and pending reply list */ + GQueue *presence_queue; + guint presence_unload_timer_id; + GList *presence_reply_list; + + /* if Disconnected has been emitted */ + gboolean disconnected; + + /* continuation line buffer */ + gchar splitbuf[IRC_MSG_MAXLEN+3]; + + /* output message queue */ + GQueue *msg_queue; + + /* UNIX time the last message was sent on */ + time_t last_msg_sent; + + /* GSource id for message queue unloading timeout */ + guint msg_queue_timeout; + + /* if idle_connection_dispose has already run once */ + gboolean dispose_has_run; +}; + +#define IDLE_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_CONNECTION, IdleConnectionPrivate)) + +static void +idle_connection_init (IdleConnection *obj) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (obj); + +#if 0 + priv->conn.fd = 0; +/* priv->conn.stream = NULL; */ + priv->conn.thread = (pthread_t)(0); + priv->conn.out_queue = NULL; + priv->conn.exit = FALSE; + priv->conn.crashed = FALSE; + priv->conn.msg_store = NULL; +#endif + + priv->port = 6667; + priv->password = NULL; + priv->realname = NULL; + priv->charset = NULL; + priv->quit_message = NULL; + priv->use_ssl = FALSE; + + priv->conn = NULL; + priv->sconn_status = SERVER_CONNECTION_STATE_NOT_CONNECTED; + + priv->ctcp_version_string = g_strdup_printf("telepathy-idle %s Telepathy IM/VoIP framework http://telepathy.freedesktop.org", TELEPATHY_IDLE_VERSION); + + priv->handles = idle_handle_storage_new(); + priv->chan_req_ctxs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + priv->im_channels = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + priv->muc_channels = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref); + + priv->polled_presences = g_intset_new(); + priv->presence_polling_timer_id = 0; + + priv->presence_queue = g_queue_new(); + priv->presence_unload_timer_id = 0; + priv->presence_reply_list = NULL; + + priv->status = TP_CONN_STATUS_DISCONNECTED; + + g_datalist_init(&(priv->cl_contact_handle_sets)); + g_datalist_init(&(priv->cl_room_handle_sets)); + + memset(priv->splitbuf, 0, IRC_MSG_MAXLEN+3); + priv->msg_queue = g_queue_new(); + priv->last_msg_sent = 0; + priv->msg_queue_timeout = 0; + + priv->disconnected = FALSE; +} + +static void idle_connection_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec); +static void idle_connection_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec); +static void idle_connection_dispose (GObject *object); +static void idle_connection_finalize (GObject *object); + +static void idle_connection_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + IdleConnection *self = (IdleConnection *)(obj); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(self); + + switch (prop_id) + { + case PROP_PROTOCOL: + { + g_free(priv->protocol); + priv->protocol = g_value_dup_string(value); + } + break; + case PROP_NICKNAME: + { + g_free(priv->nickname); + priv->nickname = g_value_dup_string(value); + + if (strlen(priv->nickname) > 9) + { + char *p = g_utf8_offset_to_pointer (priv->nickname, 9); + *p = '\0'; + g_debug("%s: nickname was longer than 9 characters; clipped to %s", G_STRFUNC, priv->nickname); + } + } + break; + case PROP_SERVER: + { + g_free(priv->server); + priv->server = g_value_dup_string(value); + } + break; + case PROP_PORT: + { + priv->port = g_value_get_uint(value); + } + break; + case PROP_PASSWORD: + { + g_free(priv->password); + priv->password = g_value_dup_string(value); + } + break; + case PROP_REALNAME: + { + g_free(priv->realname); + priv->realname = g_value_dup_string(value); + } + break; + case PROP_CHARSET: + { + g_free(priv->charset); + priv->charset = g_value_dup_string(value); + } + break; + case PROP_QUITMESSAGE: + { + g_free(priv->quit_message); + priv->quit_message = g_value_dup_string(value); + } + break; + case PROP_USE_SSL: + { + g_debug("%s: setting use_ssl to %u", G_STRFUNC, priv->use_ssl); + priv->use_ssl = g_value_get_boolean(value); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void idle_connection_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) +{ + IdleConnection *self = (IdleConnection *)(obj); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(self); + + switch (prop_id) + { + case PROP_PROTOCOL: + { + g_value_set_string(value, priv->protocol); + } + break; + case PROP_NICKNAME: + { + g_value_set_string(value, priv->nickname); + } + break; + case PROP_SERVER: + { + g_value_set_string(value, priv->server); + } + break; + case PROP_PORT: + { + g_value_set_uint(value, priv->port); + } + break; + case PROP_PASSWORD: + { + g_value_set_string(value, priv->password); + } + break; + case PROP_REALNAME: + { + g_value_set_string(value, priv->realname); + } + break; + case PROP_CHARSET: + { + g_value_set_string(value, priv->charset); + } + break; + case PROP_QUITMESSAGE: + { + g_value_set_string(value, priv->quit_message); + } + break; + case PROP_USE_SSL: + { + g_value_set_boolean(value, priv->use_ssl); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void +idle_connection_class_init (IdleConnectionClass *idle_connection_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_connection_class); + GParamSpec *param_spec; + + object_class->set_property = idle_connection_set_property; + object_class->get_property = idle_connection_get_property; + + g_type_class_add_private (idle_connection_class, sizeof (IdleConnectionPrivate)); + + object_class->dispose = idle_connection_dispose; + object_class->finalize = idle_connection_finalize; + + /* params */ + + param_spec = g_param_spec_string ("protocol", "Telepathy identifier for protocol", + "Identifier string used when the protocol " + "name is required. Unused internally.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PROTOCOL, param_spec); + + param_spec = g_param_spec_string ("nickname", "IRC nickname", + "The nickname to be visible to others in IRC.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_NICKNAME, param_spec); + + param_spec = g_param_spec_string ("server", "Hostname or IP of the IRC server to connect to", + "The server used when establishing the connection.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_SERVER, param_spec); + + param_spec = g_param_spec_uint ("port", "IRC server port", + "The destination port used when establishing the connection.", + 0, G_MAXUINT16, 6667, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PORT, param_spec); + + param_spec = g_param_spec_string ("password", "Server password", + "Password to authenticate to the server with", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PASSWORD, param_spec); + + param_spec = g_param_spec_string ("realname", "Real name", + "The real name of the user connecting to IRC", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_REALNAME, param_spec); + + param_spec = g_param_spec_string ("charset", "Character set", + "The character set to use to communicate with the outside world", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CHARSET, param_spec); + + param_spec = g_param_spec_string ("quit-message", "Quit message", + "The quit message to send to the server when leaving IRC", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_QUITMESSAGE, param_spec); + + param_spec = g_param_spec_boolean ("use-ssl", "Use SSL", + "If the connection should use a SSL tunneled socket connection", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_USE_SSL, param_spec); + + + /* signals */ +/* + signals[CAPABILITIES_CHANGED] = + g_signal_new ("capabilities-changed", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_BOXED_BOXED, + G_TYPE_NONE, 3, G_TYPE_UINT, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID)))), (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID))))); + */ + + signals[NEW_CHANNEL] = + g_signal_new ("new-channel", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__STRING_STRING_INT_INT_BOOLEAN, + G_TYPE_NONE, 5, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN); + + signals[PRESENCE_UPDATE] = + g_signal_new ("presence-update", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)))), G_TYPE_INVALID))))); + + signals[STATUS_CHANGED] = + g_signal_new ("status-changed", + G_OBJECT_CLASS_TYPE (idle_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[DISCONNECTED] = + g_signal_new("disconnected", + G_OBJECT_CLASS_TYPE(idle_connection_class), + G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[RENAMED] = + g_signal_new("renamed", + G_OBJECT_CLASS_TYPE(idle_connection_class), + G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_connection_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_connection_class), &dbus_glib_idle_connection_object_info); +} + +static void close_all_channels(IdleConnection *conn); +/*static void socket_conn_close(IdleConnection *conn);*/ + +void +idle_connection_dispose (GObject *object) +{ + IdleConnection *self = IDLE_CONNECTION (object); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (self); + DBusGProxy *dbus_proxy; + + dbus_proxy = tp_get_bus_proxy(); + + if (priv->dispose_has_run) + { + return; + } + else + { + priv->dispose_has_run = TRUE; + } + + g_debug("%s called", G_STRFUNC); + + if (priv->im_channels) + { + g_assert(g_hash_table_size(priv->im_channels) == 0); + g_hash_table_destroy(priv->im_channels); + } + + if (priv->muc_channels) + { + g_assert(g_hash_table_size(priv->muc_channels) == 0); + g_hash_table_destroy(priv->muc_channels); + } + + if (priv->presence_polling_timer_id) + { + g_source_remove(priv->presence_polling_timer_id); + priv->presence_polling_timer_id = 0; + } + + if (priv->presence_unload_timer_id) + { + g_source_remove(priv->presence_unload_timer_id); + priv->presence_unload_timer_id = 0; + } + + if (priv->msg_queue_timeout) + { + g_source_remove(priv->msg_queue_timeout); + priv->msg_queue_timeout = 0; + } + + if (priv->sconn_timeout) + { + g_source_remove(priv->sconn_timeout); + priv->sconn_timeout = 0; + } + + if (priv->conn != NULL) + { + g_warning("%s: connection was open when the object was deleted, it'll probably crash now...", G_STRFUNC); + + g_object_unref(priv->conn); + priv->conn = NULL; + } + + dbus_g_proxy_call_no_reply(dbus_proxy, "ReleaseName", G_TYPE_STRING, priv->bus_name, G_TYPE_INVALID); + + if (G_OBJECT_CLASS (idle_connection_parent_class)->dispose) + G_OBJECT_CLASS (idle_connection_parent_class)->dispose (object); +} + +static void free_helper(gpointer data, gpointer unused) +{ + return g_free(data); +} + +void +idle_connection_finalize (GObject *object) +{ + IdleConnection *self = IDLE_CONNECTION (object); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE (self); + gchar *nick; + IdleOutputPendingMsg *msg; + + g_free(priv->protocol); + g_free(priv->nickname); + g_free(priv->server); + g_free(priv->password); + g_free(priv->realname); + g_free(priv->charset); + g_free(priv->quit_message); + + g_free(priv->bus_name); + g_free(priv->object_path); + + g_free(priv->ctcp_version_string); + + g_datalist_clear(&(priv->cl_contact_handle_sets)); + g_datalist_clear(&(priv->cl_room_handle_sets)); + + g_hash_table_destroy(priv->chan_req_ctxs); + + g_intset_destroy(priv->polled_presences); + + while ((nick = g_queue_pop_head(priv->presence_queue)) != NULL) + { + g_free(nick); + } + + g_queue_free(priv->presence_queue); + + while ((msg = g_queue_pop_head(priv->msg_queue)) != NULL) + { + idle_output_pending_msg_free(msg); + } + + g_queue_free(priv->msg_queue); + + if (priv->presence_reply_list) + { + g_list_foreach(priv->presence_reply_list, free_helper, NULL); + g_list_free(priv->presence_reply_list); + } + + G_OBJECT_CLASS (idle_connection_parent_class)->finalize (object); +} + +#if 0 +void socket_conn_close(IdleConnection *conn) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (priv->conn.fd) + { + int rc; + + priv->conn.exit = TRUE; + + if ((rc = pthread_join(priv->conn.thread, NULL)) != 0) + { + g_debug("%s: pthread_join returned %d", G_STRFUNC, rc); + } + + g_async_queue_unref(priv->conn.out_queue); + + close(priv->conn.fd); + priv->conn.fd = 0; + priv->conn.crashed = FALSE; + } + else + { + g_debug("%s: connection already closed!", G_STRFUNC); + } +} +#endif + +gboolean _idle_connection_register(IdleConnection *conn, gchar **bus_name, gchar **object_path, GError **error) +{ + DBusGConnection *bus; + DBusGProxy *bus_proxy; + IdleConnectionPrivate *priv; + const char *allowed_chars = "_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + gchar *safe_proto; + gchar *unique_name; + guint request_name_result; + GError *request_error; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + bus = tp_get_bus(); + bus_proxy = tp_get_bus_proxy(); + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + safe_proto = g_strdup(priv->protocol); + g_strcanon(safe_proto, allowed_chars, '_'); + + unique_name = g_strdup_printf("%s_%s", priv->nickname, priv->server); + g_strcanon(unique_name, allowed_chars, '_'); + + priv->bus_name = g_strdup_printf(BUS_NAME ".%s.%s", safe_proto, unique_name); + priv->object_path = g_strdup_printf(OBJECT_PATH "/%s/%s", safe_proto, unique_name); + + g_free(safe_proto); + g_free(unique_name); + + g_assert(error != NULL); + + if (!dbus_g_proxy_call(bus_proxy, "RequestName", &request_error, + G_TYPE_STRING, priv->bus_name, + G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) + { + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, request_error->message); + return FALSE; + } + + if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + { + gchar *msg; + + switch (request_name_result) + { + case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: + { + msg = "Request has been queued, though we request non-queuing."; + } + break; + case DBUS_REQUEST_NAME_REPLY_EXISTS: + { + msg = "A connection manager already has this busname in use."; + } + break; + case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: + { + msg = "The connection manager already has this connection active."; + } + break; + default: + { + msg = "Unknown error return from RequestName"; + } + break; + } + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "Error acquiring bus name %s, %s", priv->bus_name, msg); + return FALSE; + } + + g_debug("%s: bus_name %s", G_STRFUNC, priv->bus_name); + + dbus_g_connection_register_g_object(bus, priv->object_path, G_OBJECT(conn)); + + g_debug("%s: object path %s", G_STRFUNC, priv->object_path); + + g_assert(bus_name != NULL); + g_assert(object_path != NULL); + + *bus_name = g_strdup(priv->bus_name); + *object_path = g_strdup(priv->object_path); + + return TRUE; +} + +IdleHandleStorage *_idle_connection_get_handles(IdleConnection *conn) +{ + IdleConnectionPrivate *priv; + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + return priv->handles; +} + +/*static void *socket_conn_thread_fn(void* conn);*/ +/*static gboolean socket_conn_open(IdleConnection *conn, GError **error);*/ +static void irc_handshakes(IdleConnection *conn); +static IdleIMChannel *new_im_channel(IdleConnection *conn, IdleHandle handle, gboolean suppress_handler); +static IdleMUCChannel *new_muc_channel(IdleConnection *conn, IdleHandle handle, gboolean suppress_handler); +static void connection_connect_cb(IdleConnection *conn, gboolean success); +static void connection_disconnect(IdleConnection *conn, TpConnectionStatusReason reason); +static void connection_disconnect_cb(IdleConnection *conn, TpConnectionStatusReason reason); +static void update_presence(IdleConnection *self, IdleHandle contact_handle, IdlePresenceState presence_id, const gchar *status_message); +static void update_presence_full(IdleConnection *self, IdleHandle contact_handle, IdlePresenceState presence_id, const gchar *status_message, guint last_activity); +static void connection_status_change(IdleConnection *conn, TpConnectionStatus status, TpConnectionStatusReason reason); +static void connection_message_cb(IdleConnection *conn, const gchar *msg); +static void emit_presence_update(IdleConnection *self, const IdleHandle *contact_handles); +static void send_irc_cmd(IdleConnection *conn, const gchar *msg); +static void handle_err_erroneusnickname(IdleConnection *conn); +static void handle_err_nicknameinuse(IdleConnection *conn); +static void priv_rename(IdleConnection *conn, guint old, guint new); + +static void handle_err_nicknameinuse(IdleConnection *conn) +{ +/* IdleConnectionPrivate *priv; + gchar *newnick; + IdleHandle handle; + gchar cmd[IRC_MSG_MAXLEN+2]; + GError *error; + gchar *tmp; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + */ + connection_status_change(conn, TP_CONN_STATUS_DISCONNECTED, TP_CONN_STATUS_REASON_NAME_IN_USE); + /* CHANGE: do not handle it ourselves anymore. Instead fail and signal UI about the situation. */ +#if 0 + g_assert(priv->nickname != NULL); + + newnick = g_strdup_printf("%s_", priv->nickname); + + g_free(priv->nickname); + + priv->nickname = newnick; + + if (!idle_connection_hton(conn, newnick, &tmp, &error)) + { + g_debug("%s: failed charset conversion, sending unconverted...: %s", G_STRFUNC, error->message); + tmp = newnick; + } + + g_snprintf(cmd, IRC_MSG_MAXLEN+2, "NICK %s", tmp); + + send_irc_cmd(conn, cmd); + + if (tmp != newnick) + { + g_free(tmp); + } + + g_debug("%s: new nickname is %s", G_STRFUNC, priv->nickname); + + handle = idle_handle_for_contact(priv->handles, newnick); + + g_assert(handle != 0); + + priv_rename(conn, priv->self_handle, handle); +#endif +} + +static void handle_err_erroneusnickname(IdleConnection *conn) +{ + connection_status_change(conn, TP_CONN_STATUS_DISCONNECTED, TP_CONN_STATUS_REASON_AUTHENTICATION_FAILED); +} + +typedef struct +{ + guint old, new; +} MUCChannelRenameData; + +static void muc_channel_rename_foreach(gpointer key, gpointer value, gpointer data) +{ + IdleMUCChannel *chan = IDLE_MUC_CHANNEL(value); + + MUCChannelRenameData *rename_data = (MUCChannelRenameData *)(data); + + _idle_muc_channel_rename(chan, rename_data->old, rename_data->new); +} + +static gboolean msg_queue_timeout_cb(gpointer user_data); + +static gboolean sconn_timeout_cb(gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + + connection_connect_cb(conn, FALSE); + connection_disconnect_cb(conn, TP_CONN_STATUS_REASON_NETWORK_ERROR); + + return FALSE; +} + +static void sconn_status_changed_cb(IdleServerConnectionIface *sconn, IdleServerConnectionState state, IdleServerConnectionStateReason reason, IdleConnection *conn) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_debug("%s: called with state %u", G_STRFUNC, state); + + TpConnectionStatusReason tp_reason; + + switch (reason) + { + case SERVER_CONNECTION_STATE_REASON_ERROR: + { + tp_reason = TP_CONN_STATUS_REASON_NETWORK_ERROR; + } + break; + case SERVER_CONNECTION_STATE_REASON_REQUESTED: + { + tp_reason = TP_CONN_STATUS_REASON_REQUESTED; + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + + if (priv->sconn_timeout) + { + g_source_remove(priv->sconn_timeout); + priv->sconn_timeout = 0; + } + + switch (state) + { + case SERVER_CONNECTION_STATE_NOT_CONNECTED: + { + if (priv->status == TP_CONN_STATUS_CONNECTING) + { + connection_connect_cb(conn, FALSE); + connection_disconnect_cb(conn, tp_reason); + } + else + { + connection_disconnect_cb(conn, reason); + } + } + break; + case SERVER_CONNECTION_STATE_CONNECTING: + { + priv->sconn_timeout = g_timeout_add(CONNECTION_TIMEOUT, sconn_timeout_cb, conn); + } + break; + case SERVER_CONNECTION_STATE_CONNECTED: + { + if ((priv->msg_queue_timeout == 0) && (g_queue_get_length(priv->msg_queue) > 0)) + { + g_debug("%s: we had messages in queue, start unloading them now", G_STRFUNC); + + priv->msg_queue_timeout = g_timeout_add(MSG_QUEUE_TIMEOUT, msg_queue_timeout_cb, conn); + } + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + + priv->sconn_status = state; +} + +static void split_message_cb(const gchar *msg, gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + gchar *converted; + GError *err; + + if (!idle_connection_ntoh(conn, msg, &converted, &err)) + { + g_debug("%s: ntoh: %s", G_STRFUNC, err->message); + g_error_free(err); + converted = (gchar *)(msg); + } + + connection_message_cb(conn, converted); + + if (converted != msg) + { + g_free(converted); + } +} + +static void sconn_received_cb(IdleServerConnectionIface *sconn, gchar *raw_msg, IdleConnection *conn) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + msg_split(raw_msg, priv->splitbuf, IRC_MSG_MAXLEN+3, split_message_cb, conn); +} + +static void priv_rename(IdleConnection *conn, guint old, guint new) +{ + IdleConnectionPrivate *priv; + MUCChannelRenameData data = {old, new}; + gpointer chan; + IdleContactPresence *cp; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (old == new) + { + g_debug("%s: tried to rename with old == new? (%u, %u)", G_STRFUNC, old, new); + + return; + } + + cp = idle_handle_get_presence(priv->handles, old); + + idle_handle_set_presence(priv->handles, new, cp); + idle_handle_set_presence(priv->handles, old, NULL); + + if (old == priv->self_handle) + { + g_debug("%s: renaming self_handle (%u, %u)", G_STRFUNC, old, new); + + idle_handle_unref(priv->handles, TP_HANDLE_TYPE_CONTACT, old); + + priv->self_handle = new; + + idle_handle_ref(priv->handles, TP_HANDLE_TYPE_CONTACT, new); + } + + chan = g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(old)); + + if (chan != NULL) + { + g_hash_table_steal(priv->im_channels, GINT_TO_POINTER(old)); + + g_hash_table_insert(priv->im_channels, GINT_TO_POINTER(new), chan); + + _idle_im_channel_rename((IdleIMChannel *)(chan), new); + } + + g_hash_table_foreach(priv->muc_channels, muc_channel_rename_foreach, &data); + + g_signal_emit(conn, signals[RENAMED], 0, old, new); +} + +gboolean _idle_connection_connect(IdleConnection *conn, GError **error) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(error != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_assert(priv->nickname != NULL); + g_assert(priv->server != NULL); + g_assert(priv->port > 0 && priv->port <= G_MAXUINT16); + + + if (priv->conn == NULL) + { + gboolean valid; + GError *conn_error = NULL; + IdleServerConnectionIface *sconn; + GType connection_type = (priv->use_ssl) + ? IDLE_TYPE_SSL_SERVER_CONNECTION + : IDLE_TYPE_SERVER_CONNECTION; + + if ((priv->self_handle = idle_handle_for_contact(priv->handles, priv->nickname)) == 0) + { + g_debug("%s: invalid nickname %s", G_STRFUNC, priv->nickname); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "invalid nickname %s", priv->nickname); + + return FALSE; + } + + valid = idle_handle_ref(priv->handles, TP_HANDLE_TYPE_CONTACT, priv->self_handle); + + g_assert(valid == TRUE); + + sconn = IDLE_SERVER_CONNECTION_IFACE(g_object_new(connection_type, + "host", priv->server, + "port", priv->port, + NULL)); + + g_signal_connect(sconn, "status-changed", (GCallback)(sconn_status_changed_cb), conn); + + if (!idle_server_connection_iface_connect(sconn, &conn_error)) + { + g_debug("%s: server connection failed to connect: %s", G_STRFUNC, conn_error->message); + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "failed to open low-level network connection: %s", conn_error->message); + + g_error_free(conn_error); + g_object_unref(sconn); + + return FALSE; + } + + priv->conn = sconn; + + g_signal_connect(sconn, "received", (GCallback)(sconn_received_cb), conn); + + irc_handshakes(conn); + + update_presence(conn, priv->self_handle, IDLE_PRESENCE_AVAILABLE, NULL); + } + else + { + g_debug("%s: conn already open!", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "connection already open!"); + + return FALSE; + } + + return TRUE; +} + +gboolean _idle_connection_send(IdleConnection *conn, const gchar *msg) +{ + send_irc_cmd(conn, msg); + + return TRUE; +} + +static gboolean msg_queue_timeout_cb(gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + int i, j; + IdleOutputPendingMsg *output_msg; + gchar msg[IRC_MSG_MAXLEN+3]; + GError *error; + + g_debug("%s: called", G_STRFUNC); + + if (priv->sconn_status != SERVER_CONNECTION_STATE_CONNECTED) + { + g_debug("%s: connection was not connected!", G_STRFUNC); + + priv->msg_queue_timeout = 0; + + return FALSE; + } + + output_msg = g_queue_peek_head(priv->msg_queue); + + if (output_msg == NULL) + { + priv->msg_queue_timeout = 0; + + return FALSE; + } + + g_strlcpy(msg, output_msg->message, IRC_MSG_MAXLEN+3); + + for (i=1; i < MSG_QUEUE_UNLOAD_AT_A_TIME; i++) + { + output_msg = g_queue_peek_nth(priv->msg_queue, i); + + if ((output_msg != NULL) && ((strlen(msg) + strlen(output_msg->message)) < IRC_MSG_MAXLEN+2)) + { + strcat(msg, output_msg->message); + } + else + { + break; + } + } + + if (idle_server_connection_iface_send(priv->conn, msg, &error)) + { + for (j=0; j<i; j++) + { + output_msg = g_queue_pop_head(priv->msg_queue); + + idle_output_pending_msg_free(output_msg); + } + + priv->last_msg_sent = time(NULL); + } + else + { + g_debug("%s: low-level network connection failed to send: %s", G_STRFUNC, error->message); + + g_error_free(error); + } + + return TRUE; +} + +/** + * Queue a IRC command for sending, clipping it to IRC_MSG_MAXLEN bytes and appending the required <CR><LF> to it + */ +static void send_irc_cmd_full(IdleConnection *conn, const gchar *msg, guint priority) +{ + gchar cmd[IRC_MSG_MAXLEN+3]; + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + int len; + GError *error; + gchar *converted; + GError *convert_error; + time_t curr_time = time(NULL); + IdleOutputPendingMsg *output_msg; + + g_assert(msg != NULL); + + /* Clip the message */ + + g_strlcpy(cmd, msg, IRC_MSG_MAXLEN+1); + + /* Append <CR><LF> */ + + len = strlen(cmd); + cmd[len++] = '\r'; + cmd[len++] = '\n'; + + cmd[len] = '\0'; + + if (!idle_connection_hton(conn, cmd, &converted, &convert_error)) + { + g_debug("%s: hton: %s", G_STRFUNC, convert_error->message); + g_error_free(convert_error); + converted = g_strdup(cmd); + } + + if ((priority == SERVER_CMD_MAX_PRIORITY) + || ((priv->status == TP_CONN_STATUS_CONNECTED) + && (priv->msg_queue_timeout == 0) + && (curr_time - priv->last_msg_sent > MSG_QUEUE_TIMEOUT))) + { + priv->last_msg_sent = curr_time; + + if (!idle_server_connection_iface_send(priv->conn, converted, &error)) + { + g_debug("%s: server connection failed to send: %s", G_STRFUNC, error->message); + g_error_free(error); + } + else + { + g_free(converted); + return; + } + } + + output_msg = idle_output_pending_msg_new(); + output_msg->message = converted; + output_msg->priority = priority; + + g_queue_insert_sorted(priv->msg_queue, output_msg, pending_msg_compare, NULL); + + if (priv->msg_queue_timeout == 0) + { + priv->msg_queue_timeout = g_timeout_add(MSG_QUEUE_TIMEOUT*1024, msg_queue_timeout_cb, conn); + } +} + +static void send_irc_cmd(IdleConnection *conn, const gchar *msg) +{ + return send_irc_cmd_full(conn, msg, SERVER_CMD_NORMAL_PRIORITY); +} + +static void cmd_parse(IdleConnection *conn, const gchar *msg); +static gchar *prefix_cmd_parse(IdleConnection *conn, const gchar *msg); +static gchar *prefix_numeric_parse(IdleConnection *conn, const gchar *msg); + +static void connection_message_cb(IdleConnection *conn, const gchar *msg) +{ + gchar scanf_fool[IRC_MSG_MAXLEN+2]; + int scanf_numeric; + int cmdcount, numericcount; + gchar *reply = NULL; + + IdleConnectionPrivate *priv; + + /*g_return_if_fail(msg != NULL); + g_return_if_fail(msg[0] != '\0');*/ + + if (msg == NULL || msg[0] == '\0') + { + return; + } + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + /*priv->conn.queue_wait = FALSE;*/ + + if (msg[0] != ':') + { + cmd_parse(conn, msg); + } + else if ((numericcount = sscanf(msg, ":%s %i %s", scanf_fool, &scanf_numeric, scanf_fool)) == 3) + { + reply = prefix_numeric_parse(conn, msg+1); + } + else if ((cmdcount = sscanf(msg, ":%s %s %s", scanf_fool, scanf_fool, scanf_fool)) == 3) + { + reply = prefix_cmd_parse(conn, msg+1); + } + else + { + g_debug("%s: unrecognized message format from server (%i cmd, %i numeric) (%s)", G_STRFUNC, cmdcount, numericcount, msg); + return; + } + + if (reply) + { + send_irc_cmd (conn, reply); + g_free (reply); + } +} + +static void cmd_parse(IdleConnection *conn, const gchar *msg) +{ + if ((g_strncasecmp(msg, "PING ", 5) == 0) && (msg[5] != '\0')) + { + /* PING command, reply ... */ + gchar *reply = g_strdup_printf("PONG %s", msg+5); + send_irc_cmd_full (conn, reply, SERVER_CMD_MAX_PRIORITY); + g_free (reply); + } + else + { + g_debug("%s: ignored unparsed message from server (%s)", G_STRFUNC, msg); + } +} + +static void muc_channel_handle_quit_foreach(gpointer key, gpointer value, gpointer user_data) +{ + IdleMUCChannel *chan = value; + IdleHandle handle = GPOINTER_TO_INT(user_data); + + g_debug("%s: for %p %p %p", G_STRFUNC, key, value, user_data); + + _idle_muc_channel_handle_quit(chan, handle, TRUE, handle, TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE); +} + +static gchar *prefix_cmd_parse(IdleConnection *conn, const gchar *msg) +{ + IdleConnectionPrivate *priv; + guint tokenc; + gchar *reply = NULL; + + gchar **tokens; + gchar *sender = NULL, *cmd = NULL, *recipient = NULL; + gchar *temp = NULL; + gchar *from = NULL; + gunichar ucs4char; + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + tokens = g_strsplit_set(msg, " ", -1); + for (tokenc = 0; tokens[tokenc] != NULL; tokenc++) {} + + sender = tokens[0]; + cmd = tokens[1]; + recipient = tokens[2]; + + if (!sender || !cmd || !recipient) + { + g_debug("%s: failed to parse message (%s) into sender, cmd and recipient", G_STRFUNC, msg); + + goto cleanupl; + } + + if ((temp = strchr(sender, '!')) > sender) + { + from = g_strndup(sender, temp-sender); + } + else + { + if ((temp = strchr(sender, '@')) > sender) + { + from = g_strndup(sender, temp-sender); + } + else + { + from = sender; + } + } + + if ((g_strncasecmp(cmd, "NOTICE", 6) == 0) || (g_strncasecmp(cmd, "PRIVMSG", 7) == 0)) + { + IdleHandle handle; + gchar *body; + TpChannelTextMessageType msgtype; + + if ((body = strstr(msg, " :")+1) != NULL) + { + body++; + } + else + { + g_debug("%s got NOTICE/PRIVMSG with missing body identifier \" :\" (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + if (cmd[0] == 'N') + { + msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE; + } + else + { + if (body[0] == '\001') + { + char *suffix; + + body++; + + suffix = strrchr(body, '\001'); + + g_assert(suffix != NULL); + + *suffix = '\0'; + + if (!g_strncasecmp(body, "ACTION ", 7)) + { + g_debug("%s: detected CTCP ACTION message", G_STRFUNC); + + body += 7; + + msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION; + } + else if (!g_strncasecmp(body, "VERSION", 7)) + { + g_debug("%s: detected CTCP VERSION message", G_STRFUNC); + + reply = g_strdup_printf("NOTICE %s :\001VERSION %s\001", from, priv->ctcp_version_string); + + goto cleanupl; + } + else + { + g_debug("%s: ignored unimplemented (non-ACTION/VERSION) CTCP (%s)", G_STRFUNC, body); + goto cleanupl; + } + } + else + { + msgtype = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; + } + } + + ucs4char = g_utf8_get_char_validated(recipient, -1); + + if (g_unichar_isalpha(ucs4char)) + { + IdleIMChannel *chan; + + if ((handle = idle_handle_for_contact(priv->handles, from)) == 0) + { + g_debug("%s: got NOTICE/PRIVMSG with malformed sender %s (%s)", G_STRFUNC, from, msg); + goto cleanupl; + } + + g_debug("%s: receiving msg with type %u from %s (handle %u): %s", G_STRFUNC, msgtype, from, handle, body); + + if ((chan = g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle))) == NULL) + { + chan = new_im_channel(conn, handle, FALSE); + + g_debug("%s: spawning new IdleIMChannel (address %p handle %u)", G_STRFUNC, chan, handle); + } + else + { + g_debug("%s: receiving thru existing IdleIMChannel %p", G_STRFUNC, chan); + } + + _idle_im_channel_receive(chan, msgtype, handle, body); + } + else if ((recipient[0] == '#') || (recipient[0] == '&') || (recipient[0] == '!') || (recipient[0] == '+')) + { + IdleMUCChannel *chan; + IdleHandle sender_handle; + + if ((handle = idle_handle_for_room(priv->handles, recipient)) == 0) + { + g_debug("%s: got NOTICE/PRIVMSG with malformed IRC channel recipient %s (%s)", G_STRFUNC, from, msg); + goto cleanupl; + } + + if ((sender_handle = idle_handle_for_contact(priv->handles, from)) == 0) + { + g_debug("%s: got NOTICE/PRIVMSG with malformed sender %s (%s)", G_STRFUNC, from, msg); + goto cleanupl; + } + + g_debug("%s: receiving NOTICE/PRIVMSG from %s (handle %u) to MUCChannel %s (handle %u): %s", G_STRFUNC, from, sender_handle, recipient, handle, body); + + if ((chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle))) == NULL) + { + g_debug("%s: spawning new IdleMUCChannel", G_STRFUNC); + + chan = new_muc_channel(conn, handle, FALSE); + } + + _idle_muc_channel_receive(chan, msgtype, sender_handle, body); + } + else + { + g_debug("%s: ignored NOTICE/PRIVMSG from invalid sender (%s)", G_STRFUNC, from); + } + } + else if (g_strncasecmp(cmd, "JOIN", 4) == 0) + { + IdleHandle handle; + char *channel = recipient; + IdleMUCChannel *chan; + + /* I see at least irc.paivola.fi (UnrealIRCD 3.2.4) sending the channel name with a : prefix although it doesn't say that in the RFC */ + if (channel[0] == ':') + { + channel++; + } + + handle = idle_handle_for_room(priv->handles, channel); + + if (handle == 0) + { + g_debug("%s: received JOIN with malformed channel (%s)", G_STRFUNC, channel); + g_free(channel); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: got JOIN message for channel we don't have in muc_channels? (%s)", G_STRFUNC, channel); + g_free(channel); + goto cleanupl; + } + + g_debug("%s: got JOIN for IRC channel %s (handle %u)", G_STRFUNC, channel, handle); + + _idle_muc_channel_join(chan, from); + } + else if (g_strncasecmp(cmd, "NICK", 4) == 0) + { + IdleHandle old_handle, new_handle; + gchar *old_down, *new_down; + + old_down = from; + old_handle = idle_handle_for_contact(priv->handles, old_down); + + new_down = recipient+1; + new_handle = idle_handle_for_contact(priv->handles, new_down); + + g_debug("%s: got NICK (%s) -> (%s), %u -> %u", G_STRFUNC, old_down, new_down, old_handle, new_handle); + + priv_rename(conn, old_handle, new_handle); + } + else if (g_strncasecmp(cmd, "MODE", 4) == 0) + { + IdleHandle handle; + IdleMUCChannel *chan; + gchar *tmp; + ucs4char = g_utf8_get_char_validated(recipient, -1); + + if (g_unichar_isalpha(ucs4char)) + { + g_debug("%s: got user MODE message, ignoring...", G_STRFUNC); + goto cleanupl; + } + + handle = idle_handle_for_room(priv->handles, recipient); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in MODE", G_STRFUNC, recipient); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s got MODE message for channel we don't have in muc_channels (%s)", G_STRFUNC, recipient); + goto cleanupl; + } + + tmp = strstr(msg, recipient); + g_assert(tmp != NULL); + + tmp = strchr(tmp+1, ' '); + g_assert(tmp != NULL); + + tmp++; + + g_debug("%s: got MODE for (%s) (%s)", G_STRFUNC, recipient, tmp); + + _idle_muc_channel_mode(chan, tmp); + } + else if (g_strncasecmp(cmd, "PART", 4) == 0) + { + IdleHandle handle; + IdleMUCChannel *chan; + gchar *chan_down; + + chan_down = recipient; + + handle = idle_handle_for_room(priv->handles, chan_down); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in PART", G_STRFUNC, chan_down); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: got PART message for channel we don't have in muc_channels? (%s)", G_STRFUNC, chan_down); + goto cleanupl; + } + + g_debug("%s: got PART for (%s) in (%s)", G_STRFUNC, from, chan_down); + + _idle_muc_channel_part(chan, from); + } + else if (g_strncasecmp(cmd, "KICK", 4) == 0) + { + IdleHandle handle; + IdleMUCChannel *chan; + gchar *chan_down; + gchar *nick; + gchar *tmp; + int i; + + chan_down = recipient; + + handle = idle_handle_for_room(priv->handles, chan_down); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in KICK/PART", G_STRFUNC, chan_down); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: got KICK/PART message for channel we don't have in muc_channels? (%s)", G_STRFUNC, chan_down); + goto cleanupl; + } + + nick = strchr(msg, ' '); + + for (i=0; i<2; i++) + { + if (nick == NULL) + { + g_debug("%s: failed to find nick in KICK message (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + nick = strchr(nick+1, ' '); + } + + if (nick == NULL) + { + g_debug("%s: 1failed to find nick in KICK message (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + nick++; + + tmp = strchr(nick, ' '); + + if (tmp == NULL) + { + g_debug("%s: 2failed to find nick in KICK message (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + *tmp = '\0'; + + tmp = strchr(sender, '!'); + + if (tmp) + { + *tmp = '\0'; + } + + g_debug("%s: got KICK for (%s) by (%s) in (%s)", G_STRFUNC, nick, sender, chan_down); + + _idle_muc_channel_kick(chan, nick, sender, TP_CHANNEL_GROUP_CHANGE_REASON_KICKED); + } + else if (g_strncasecmp(cmd, "INVITE", 6) == 0) + { + IdleHandle handle; + IdleMUCChannel *chan; + gboolean worked_around = FALSE; + gchar *tmp; + IdleHandle inviter_handle; + + if (tokenc != 4) + { + g_debug("%s: got INVITE with tokenc != 4, ignoring...", G_STRFUNC); + goto cleanupl; + } + + /* workaround for non-conformant servers... */ + if (tokens[3][0] == ':') + { + tokens[3]++; + worked_around = TRUE; + } + + handle = idle_handle_for_room(priv->handles, tokens[3]); + + if (worked_around) + { + tokens[3]--; + } + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s) in INVITE", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + tmp = strchr(tokens[0], '!'); + + if (tmp) + { + *tmp = '\0'; + } + + inviter_handle = idle_handle_for_contact(priv->handles, tokens[0]); + + if (!inviter_handle) + { + g_debug("%s: failed to get handle for inviter (%s) in INVITE", G_STRFUNC, tokens[0]); + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan != NULL) + { + g_debug("%s: got INVITE for a channel we are already on, ignoring...", G_STRFUNC); + goto cleanupl; + } + + chan = new_muc_channel(conn, handle, FALSE); + + g_assert(chan != NULL); + + _idle_muc_channel_invited(chan, inviter_handle); + + g_debug("%s: got INVITEd to channel (%s) (handle %u)", G_STRFUNC, tokens[3], handle); + } + else if (g_strncasecmp(cmd, "QUIT", 4) == 0) + { + IdleHandle handle; + IdleIMChannel *chan; + + handle = idle_handle_for_contact(priv->handles, from); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in QUIT", G_STRFUNC, from); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle)); + + if (chan != NULL) + { + GError *error; + + g_debug("%s: contact QUIT, closing IMChannel...", G_STRFUNC); + + if (!idle_im_channel_close(chan, &error)) + { + g_debug("%s: idle_im_channel_close failed: %s", G_STRFUNC, error->message); + g_error_free(error); + } + } + + g_hash_table_foreach(priv->muc_channels, muc_channel_handle_quit_foreach, GINT_TO_POINTER(handle)); + } + else if (g_strncasecmp(cmd, "TOPIC", 5) == 0) + { + IdleHandle handle, setter_handle; + IdleMUCChannel *chan; + char *tmp; + + handle = idle_handle_for_room(priv->handles, recipient); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in TOPIC", G_STRFUNC, recipient); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: got NULL MUCChannel for (%s) (handle %u)", G_STRFUNC, recipient, handle); + goto cleanupl; + } + + tmp = strstr(msg, " :")+1; + + if (tmp == NULL) + { + g_debug("%s: could not find body identifier in TOPIC message (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + setter_handle = idle_handle_for_contact(priv->handles, from); + + if (setter_handle == 0) + { + g_debug("%s: could not get handle for (%s) in TOPIC", G_STRFUNC, from); + goto cleanupl; + } + + if (tmp+1 != '\0') + { + _idle_muc_channel_topic_full(chan, setter_handle, time(NULL), tmp+1); + } + else + { + _idle_muc_channel_topic_unset(chan); + } + + g_debug("%s: got TOPIC for (%s)", G_STRFUNC, recipient); + } + else + { + g_debug("%s: ignored unparsed message from server (%s) = (%s, %s, %s)", G_STRFUNC, msg, sender, cmd, recipient); + } + +cleanupl: + + if (from != sender) + { + g_free(from); + } + + g_strfreev(tokens); + + return reply; +} + +#define IRC_RPL_WELCOME 001 +#define IRC_RPL_AWAY 301 +#define IRC_RPL_UNAWAY 305 +#define IRC_RPL_NOWAWAY 306 +#define IRC_RPL_WHOISIDLE 317 +#define IRC_RPL_ENDOFWHOIS 318 +#define IRC_RPL_MODEREPLY 324 +#define IRC_RPL_TOPIC 332 +#define IRC_RPL_TOPIC_STAMP 333 +#define IRC_RPL_NAMEREPLY 353 +#define IRC_ERR_NOSUCHNICK 401 +#define IRC_ERR_CANNOTSENDTOCHAN 404 +#define IRC_ERR_ERRONEOUSNICKNAME 432 +#define IRC_ERR_NICKNAMEINUSE 433 +#define IRC_ERR_CHANNELISFULL 471 +#define IRC_ERR_INVITEONLYCHAN 473 +#define IRC_ERR_BANNEDFROMCHAN 474 +#define IRC_ERR_BADCHANNELKEY 475 + +static gint strcasecmp_helper(gconstpointer a, gconstpointer b) +{ + return strcasecmp(a, b); +} + +static gchar *prefix_numeric_parse(IdleConnection *conn, const gchar *msg) +{ + IdleConnectionPrivate *priv; + + gchar *reply = NULL; + + gchar **tokens; + guint numeric; + gchar *sender, *recipient; + int tokenc; + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + tokens = g_strsplit_set(msg, " ", -1); + + for (tokenc = 0; tokens[tokenc] != NULL; tokenc++) {} + + if (tokenc < 3) + { + g_debug("%s: got message with less than 3 tokens (%s), ignoring...", G_STRFUNC, msg); + + goto cleanupl; + } + + sender = tokens[0]; + numeric = atoi(tokens[1]); + recipient = tokens[2]; + + if (!sender || !recipient) + { + goto cleanupl; + } + + if (numeric == IRC_RPL_NAMEREPLY) + { + char *identifier; + char *channeltmp; + char *channel; + IdleHandle handle; + GArray *names_array; + guint i, lasti; + guint length; + IdleMUCChannel *chan; + + if ((identifier = strchr(msg, '=')) != NULL) + { + g_debug("%s: found identifier for PUBLIC channel at %p", G_STRFUNC, identifier); + } + else if ((identifier = strchr(msg, '*')) != NULL) + { + g_debug("%s: found identifier for PRIVATE channel at %p", G_STRFUNC, identifier); + } + else if ((identifier = strchr(msg, '@')) != NULL) + { + g_debug("%s: found identifier for SECRET channel at %p", G_STRFUNC, identifier); + } + else + { + g_debug("%s: did not find channel type identifier in NAMES msg, ignoring", G_STRFUNC); + goto cleanupl; + } + + channeltmp = identifier+1; + + identifier = strstr(identifier, " :")+1; + + if (!identifier) + { + g_debug("%s: did not find body identifier in NAMES msg, ignoring", G_STRFUNC); + goto cleanupl; + } + + *identifier = '\0'; + + channeltmp = g_strdup(channeltmp); + + channel = g_strstrip(channeltmp); + + handle = idle_handle_for_room(priv->handles, channel); + + if (handle == 0) + { + g_debug("%s: got invalid channel (%s) in NAMES, ignoring", G_STRFUNC, channel); + + g_free(channeltmp); + + goto cleanupl; + } + + names_array = g_array_new(FALSE, FALSE, sizeof(gchar *)); + + identifier++; + + length = strlen(identifier); + lasti = 0; + + for (i=0; i < length; i++) + { + if (identifier[i] == ' ') + { + if (i>lasti) + { + gchar *tmp; + + identifier[i] = '\0'; + + tmp = g_strdup(identifier+lasti); + + g_array_append_val(names_array, tmp); + } + + lasti = i+1; + } + } + + chan = (IdleMUCChannel *)(g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle))); + + if (chan == NULL) + { + g_debug("%s: got NAMES for channel (%s) we're not on but have a handle (%u) for?", G_STRFUNC, channel, handle); + + g_free(channeltmp); + + goto cleanupl; + } + + _idle_muc_channel_names(chan, names_array); + + g_debug("%s: parsed %u names for channel %s (handle %u)", G_STRFUNC, names_array->len, channel, handle); + + g_free(channeltmp); + + for (i=0; i<names_array->len; i++) + { + g_free(g_array_index(names_array, gchar *, i)); + } + g_array_free(names_array, TRUE); + } + else if (numeric == IRC_ERR_BADCHANNELKEY) + { + gchar *channel; + gchar *tmp; + IdleHandle handle; + IdleMUCChannel *chan; + + if (((channel = strchr(msg, '#')) == NULL) && + ((channel = strchr(msg, '!')) == NULL) && + ((channel = strchr(msg, '&')) == NULL) && + ((channel = strchr(msg, '+')) == NULL)) + { + g_debug("%s: didn't find valid channel name in ERR_BADCHANNELKEY (%s), ignoring", G_STRFUNC, msg); + goto cleanupl; + } + + if ((tmp = strstr(channel, " :")+1) == NULL) + { + g_debug("%s: failed to find : separator in ERR_BADCHANNELKEY (%s), ignoring", G_STRFUNC, msg); + goto cleanupl; + } + + *tmp = '\0'; + + channel = g_strstrip(channel); + + handle = idle_handle_for_room(priv->handles, channel); + + if (handle == 0) + { + g_debug("%s: failed to get handle for channel %s in ERR_BADCHANNELKEY (%s)", G_STRFUNC, channel, msg); + goto cleanupl; + } + + chan = (IdleMUCChannel *)(g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle))); + + if (chan == NULL) + { + g_debug("%s: got ERR_BADCHANNELKEY for channel %s we are not on yet have a handle (%u) for?", G_STRFUNC, channel, handle); + goto cleanupl; + } + + _idle_muc_channel_badchannelkey(chan); + + g_debug("%s: got ERR_BADCHANNELKEY for channel %s (handle %u)", G_STRFUNC, channel, handle); + } + else if (numeric == IRC_ERR_ERRONEOUSNICKNAME) + { + g_debug("%s: got ERR_ERROUNEUSNICKNAME", G_STRFUNC); + + handle_err_erroneusnickname(conn); + } + else if (numeric == IRC_ERR_NICKNAMEINUSE) + { + g_debug("%s: got ERR_NICKNAMEINUSE", G_STRFUNC); + + handle_err_nicknameinuse(conn); + } + else if (numeric == IRC_RPL_WELCOME) + { + g_debug("%s: got RPL_WELCOME", G_STRFUNC); + + connection_connect_cb(conn, TRUE); + } + else if (numeric == IRC_RPL_TOPIC) + { + IdleHandle handle; + IdleMUCChannel *chan; + gchar *tmp; + + if (tokenc < 5) + { + g_debug("%s: not enough tokens for RPL_TOPIC in (%s), ignoring...", G_STRFUNC, msg); + goto cleanupl; + } + + handle = idle_handle_for_room(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s) in RPL_TOPIC", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: failed to find channel (%s) (handle %u) in RPL_TOPIC", G_STRFUNC, tokens[3], handle); + goto cleanupl; + } + + tmp = strstr(msg, " :")+1; + + if (tmp == NULL) + { + g_debug("%s: failed to find body separator in (%s)", G_STRFUNC, msg); + + goto cleanupl; + } + + _idle_muc_channel_topic(chan, tmp+1); + + g_debug("%s: got RPL_TOPIC for (%s)", G_STRFUNC, tokens[3]); + } + else if (numeric == IRC_RPL_TOPIC_STAMP) + { + IdleHandle handle, toucher_handle; + IdleMUCChannel *chan; + guint timestamp; + + if (tokenc != 6) + { + g_debug("%s: wrong amount of tokens (%u) for RPL_TOPICSTAMP in (%s), ignoring...", G_STRFUNC, tokenc, msg); + goto cleanupl; + } + + handle = idle_handle_for_room(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s) in RPL_TOPICSTAMP", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: failed to find channel (%s) (handle %u) in RPL_TOPICSTAMP", G_STRFUNC, tokens[3], handle); + goto cleanupl; + } + + toucher_handle = idle_handle_for_contact(priv->handles, tokens[4]); + + if (toucher_handle == 0) + { + g_debug("%s: failed to get handle for toucher (%s) in RPL_TOPICSTAMP", G_STRFUNC, tokens[4]); + goto cleanupl; + } + + if (sscanf(tokens[5], "%u", ×tamp) != 1) + { + g_debug("%s: failed to parse (%s) to uint in RPL_TOPICSTAMP", G_STRFUNC, tokens[5]); + goto cleanupl; + } + + _idle_muc_channel_topic_touch(chan, toucher_handle, timestamp); + + g_debug("%s: got RPL_TOPICSTAMP for (%s)", G_STRFUNC, tokens[3]); + } + else if (numeric == IRC_RPL_MODEREPLY) + { + IdleHandle handle; + IdleMUCChannel *chan; + gchar *tmp; + + if (tokenc < 5) + { + g_debug("%s: got IRC_RPL_MODEREPLY with less than 5 tokens, ignoring... (%s)", G_STRFUNC, msg); + goto cleanupl; + } + + handle = idle_handle_for_room(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in MODE", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s got MODE message for channel we don't have in muc_channels (%s)", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + tmp = strstr(msg, tokens[3]); + g_assert(tmp != NULL); + + tmp = strchr(tmp+1, ' '); + g_assert(tmp != NULL); + + tmp++; + + g_debug("%s: got RPL_MODEREPLY for (%s) (%s)", G_STRFUNC, tokens[3], tmp); + + _idle_muc_channel_mode(chan, tmp); + } + else if (numeric == IRC_ERR_NOSUCHNICK) + { + IdleHandle handle; + IdleIMChannel *chan; + GList *link; + + if (tokenc < 4) + { + g_debug("%s: got ERR_NOSUCHNICK with tokenc < 4, ignoring...", G_STRFUNC); + goto cleanupl; + } + + if ((link = g_list_find_custom(priv->presence_reply_list, tokens[3], strcasecmp_helper)) != NULL) + { + g_debug("%s: one of the contacts we asked presence for was offline, removing query...", G_STRFUNC); + g_free(link->data); + priv->presence_reply_list = g_list_delete_link(priv->presence_reply_list, link); + } + + handle = idle_handle_for_contact(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in ERR_NOSUCHNICK", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + update_presence(conn, handle, IDLE_PRESENCE_OFFLINE, NULL); + + chan = g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: could not get IMChannel for (%s) (handle %u) in ERR_NOSUCHNICK", G_STRFUNC, tokens[3], handle); + goto cleanupl; + } + + g_debug("%s: got ERR_NOSUCHNICK for (%s) (handle %u)", G_STRFUNC, tokens[3], handle); + + _idle_im_channel_nosuchnick(chan); + } + else if (numeric == IRC_ERR_BANNEDFROMCHAN + || numeric == IRC_ERR_CHANNELISFULL + || numeric == IRC_ERR_INVITEONLYCHAN) + { + IdleHandle handle; + IdleMUCChannel *chan; + guint err; + + handle = idle_handle_for_room(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s) in join errors", G_STRFUNC, tokens[2]); + goto cleanupl; + } + + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + + if (chan == NULL) + { + g_debug("%s: failed to find channel with handle %u in join errors", G_STRFUNC, handle); + goto cleanupl; + } + + switch (numeric) + { + case IRC_ERR_BANNEDFROMCHAN: + { + err = MUC_CHANNEL_JOIN_ERROR_BANNED; + } + break; + case IRC_ERR_CHANNELISFULL: + { + err = MUC_CHANNEL_JOIN_ERROR_FULL; + } + break; + case IRC_ERR_INVITEONLYCHAN: + { + err = MUC_CHANNEL_JOIN_ERROR_INVITE_ONLY; + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + + _idle_muc_channel_join_error(chan, err); + } + else if (numeric == IRC_RPL_NOWAWAY + || numeric == IRC_RPL_UNAWAY) + { + update_presence(conn, priv->self_handle, + (numeric == IRC_RPL_UNAWAY) ? IDLE_PRESENCE_AVAILABLE : IDLE_PRESENCE_AWAY, NULL); + + g_debug("%s: got away state change", G_STRFUNC); + } + else if (numeric == IRC_RPL_AWAY) + { + IdleHandle handle; + char *tmp; + GList *link; + + if (tokenc < 5) + { + g_debug("%s: got RPL_AWAY with tokenc < 3, ignoring...", G_STRFUNC); + goto cleanupl; + } + + handle = idle_handle_for_contact(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in IRC_RPL_AWAY...", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + tmp = strstr(msg, " :")+1; + + if (tmp == NULL) + { + g_debug("%s: failed to find body separator in RPL_AWAY, ignoring...", G_STRFUNC); + goto cleanupl; + } + + g_debug("%s: got RPL_AWAY for %u", G_STRFUNC, handle); + + if ((link = g_list_find_custom(priv->presence_reply_list, tokens[3], strcasecmp_helper)) != NULL) + { + g_debug("%s: got RPL_AWAY for someone we had queried presence for...", G_STRFUNC); + g_free(link->data); + priv->presence_reply_list = g_list_remove_link(priv->presence_reply_list, link); + } + + update_presence(conn, handle, IDLE_PRESENCE_AWAY, tmp+1); + } + else if (numeric == IRC_RPL_ENDOFWHOIS) + { + GList *link; + IdleHandle handle; + + if (tokenc < 4) + { + g_debug("%s: got IRC_RPL_ENDOFWHOIS with <4 tokens, ignoring...", G_STRFUNC); + goto cleanupl; + } + + if ((link = g_list_find_custom(priv->presence_reply_list, tokens[3], strcasecmp_helper)) != NULL) + { + g_debug("%s: got end of whois for someone we have queried presence for but haven't got away/offline yet -> available", G_STRFUNC); + + g_free(link->data); + priv->presence_reply_list = g_list_remove_link(priv->presence_reply_list, link); + + handle = idle_handle_for_contact(priv->handles, tokens[3]); + + if (handle == 0) + { + g_debug("%s: could not get handle for (%s) in IRC_RPL_ENDOFWHOIS...", G_STRFUNC, tokens[3]); + goto cleanupl; + } + + update_presence(conn, handle, IDLE_PRESENCE_AVAILABLE, NULL); + } + } + else if (numeric == IRC_RPL_WHOISIDLE) + { + IdleHandle handle; + IdleContactPresence *cp; + guint last_activity; + + if (tokenc < 5) + { + g_debug("%s: got IRC_RPL_WHOISIDLE with tokenc < 5, ignoring...", G_STRFUNC); + goto cleanupl; + } + + handle = idle_handle_for_contact(priv->handles, tokens[2]); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s) in RPL_WHOISIDLE, ignoring...", G_STRFUNC, tokens[2]); + goto cleanupl; + } + + cp = idle_handle_get_presence(priv->handles, handle); + + if (cp == NULL) + { + g_debug("%s: failed to get cp in RPL_WHOISIDLE, ignoring...", G_STRFUNC); + goto cleanupl; + } + + last_activity = time(NULL) - atoi(tokens[3]); + + update_presence_full(conn, handle, cp->presence_state, NULL, last_activity); + + g_debug("%s: got RPL_WHOISIDLE for (%s) (handle %u)", G_STRFUNC, tokens[2], handle); + } + else + { + g_debug("%s: ignored unparsed message from server (%s)", G_STRFUNC, msg); + } + +cleanupl: + + g_strfreev(tokens); + + return reply; +} + +static void irc_handshakes(IdleConnection *conn) +{ + IdleConnectionPrivate *priv; + gchar msg[IRC_MSG_MAXLEN+1]; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (priv->password != NULL) + { + g_snprintf(msg, IRC_MSG_MAXLEN+1, "PASS %s", priv->password); + + send_irc_cmd(conn, msg); + } + + g_snprintf(msg, IRC_MSG_MAXLEN+1, "NICK %s", priv->nickname); + send_irc_cmd(conn, msg); + + g_snprintf(msg, IRC_MSG_MAXLEN+1, "USER %s %u * :%s", priv->nickname, 8, priv->realname); + send_irc_cmd(conn, msg); +} + +static void connection_connect_cb(IdleConnection *conn, gboolean success) +{ + g_assert(conn != NULL); + + if (success) + { + connection_status_change(conn, TP_CONN_STATUS_CONNECTED, TP_CONN_STATUS_REASON_REQUESTED); + } + else + { + connection_status_change(conn, TP_CONN_STATUS_DISCONNECTED, TP_CONN_STATUS_REASON_NETWORK_ERROR); + } +} + +/* 2 minutes */ +#define PRESENCE_POLLING_TIMEOUT 2*60*1000 + +static void presence_request_push(IdleConnection *obj, const gchar *nick); + +static void polling_foreach_func(guint i, gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + IdleHandle handle = (IdleHandle)(i); + + if (idle_handle_is_valid(priv->handles, TP_HANDLE_TYPE_CONTACT, handle)) + { + const gchar *nick = idle_handle_inspect(priv->handles, TP_HANDLE_TYPE_CONTACT, handle); + + presence_request_push(conn, nick); + } + else + { + g_intset_remove(priv->polled_presences, handle); + } +} + +static gboolean presence_polling_cb(gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(user_data); + + g_intset_foreach(priv->polled_presences, polling_foreach_func, conn); + + if (!g_intset_size(priv->polled_presences)) + { + return FALSE; + } + + return TRUE; +} + +static void polled_presence_add(IdleConnection *conn, IdleHandle handle) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_intset_add(priv->polled_presences, handle); + + if (!priv->presence_polling_timer_id) + { + presence_polling_cb(conn); + priv->presence_polling_timer_id = g_timeout_add(PRESENCE_POLLING_TIMEOUT, presence_polling_cb, conn); + } +} + +static void polled_presence_remove(IdleConnection *conn, IdleHandle handle) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_intset_remove(priv->polled_presences, handle); + + if (priv->presence_polling_timer_id && !g_intset_size(priv->polled_presences)) + { + g_source_remove(priv->presence_polling_timer_id); + priv->presence_polling_timer_id = 0; + } +} + +static void update_presence(IdleConnection *self, IdleHandle contact_handle, IdlePresenceState presence_state, const gchar *status_message) +{ + return update_presence_full(self, contact_handle, presence_state, status_message, 0); +} + +static void update_presence_full(IdleConnection *self, IdleHandle contact_handle, IdlePresenceState presence_state, const gchar *status_message, guint last_activity) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(self); + IdleContactPresence *cp = idle_handle_get_presence(priv->handles, contact_handle); + IdleHandle handles[2] = {contact_handle, 0}; + + if (cp) + { + if (cp->presence_state == presence_state && + ((cp->status_message == NULL && status_message == NULL) || + (cp->status_message != NULL && status_message != NULL && + strcmp(cp->status_message, status_message) == 0))) + { + return; + } + + if (presence_state == IDLE_PRESENCE_AVAILABLE) + { + polled_presence_remove(self, contact_handle); + idle_contact_presence_free(cp); + idle_handle_set_presence(priv->handles, contact_handle, NULL); + } + } + else if (presence_state != IDLE_PRESENCE_AVAILABLE) + { + cp = idle_contact_presence_new0(); + g_assert(idle_handle_set_presence(priv->handles, contact_handle, cp)); + + polled_presence_add(self, contact_handle); + } + else + { + goto emit; + } + + cp->presence_state = presence_state; + + g_free(cp->status_message); + + if (status_message && status_message[0] != '\0') + { + cp->status_message = g_strdup(status_message); + } + else + { + cp->status_message = NULL; + } + + if (last_activity != 0) + { + cp->last_activity = last_activity; + } + +emit: + emit_presence_update(self, handles); +} + + +#if 0 +static gboolean socket_conn_open(IdleConnection *conn, GError **error) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(error != NULL); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (priv->conn.fd) + { + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "Connection already open!"); + return FALSE; + } + else + { + int fd; + pthread_t thread; + int rc; + struct sockaddr_in sin = {0}; + + sin.sin_family = AF_INET; + sin.sin_port = htons(INADDR_ANY); + sin.sin_addr.s_addr = INADDR_ANY; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + g_debug("%s: socket() failed: %s", G_STRFUNC, strerror(errno)); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "socket() failed: %s", strerror(errno)); + + return FALSE; + } + + if (bind(fd, (struct sockaddr *)(&sin), sizeof(sin)) == -1) + { + g_debug("%s: bind() failed: %s", G_STRFUNC, strerror(errno)); + + close(fd); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "bind() failed: %s", strerror(errno)); + + return FALSE; + } + + priv->conn.fd = fd; + priv->conn.out_queue = g_async_queue_new(); + priv->conn.exit = FALSE; + priv->conn.crashed = FALSE; + priv->conn.queue_wait = TRUE; + + if ((rc = pthread_create(&thread, NULL, socket_conn_thread_fn, conn)) != 0) + { + priv->conn.fd = 0; + g_async_queue_unref(priv->conn.out_queue); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "Failed to create a thread for the socket connection: %s", strerror(rc)); + + return FALSE; + } + + priv->conn.thread = thread; + + g_debug("%s: connection (likely) has been opened", G_STRFUNC); + + return TRUE; + } +} + +/** + * Write everything in a queue to a fd + * Returns TRUE on success and FALSE on failure (network error, connection closed etc) + */ + +static gboolean send_all_queued(int fd, GAsyncQueue *queue); + +/** + * Read a line (terminated by <CR><LF>) from <stream> and make it available to the caller via <msg> + * A NULL *<msg> signals the last line in a batch + * Returns TRUE on success and FALSE on network error (connection closed etc) + * + * Performs a poll() on <pollfd> with timeout == 0.5 sec, so if no messages are incoming, it also serves as a sleep point. + */ + +static gboolean receive_line(int fd, gchar **msg, int poll_fd); + +static gboolean threaded_connection_open(IdleConnection *conn, GError **error); + +static gboolean threaded_connection_open(IdleConnection *conn, GError **error) +{ + IdleConnectionPrivate *priv; + struct sockaddr_in sin = {0}; + struct hostent *dst_host; +/* FILE *stream;*/ + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if ((dst_host = gethostbyname(priv->server)) == NULL) + { + close(priv->conn.fd); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "Failed to resolve server %s", priv->server); + + return FALSE; + } + + sin.sin_family = AF_INET; + sin.sin_port = htons(priv->port); + sin.sin_addr = *((struct in_addr *)(dst_host->h_addr)); + + if (connect(priv->conn.fd, (struct sockaddr *)(&sin), sizeof(sin)) == -1) + { + close(priv->conn.fd); + + g_debug("%s: connect() failed: %s", G_STRFUNC, strerror(errno)); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "connect() failed: %s", strerror(errno)); + + return FALSE; + } +/* + if ((stream = fdopen(priv->conn.fd, "r+")) == NULL) + { + close(priv->conn.fd); + + g_debug("%s: fdopen() failed: %s", G_STRFUNC, strerror(errno)); + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "fdopen() failed: %s", strerror(errno)); + + return FALSE; + } + + priv->conn.stream = stream; +*/ + g_debug("%s: success?", G_STRFUNC); + + return TRUE; +} + +static void msg_split(IdleConnection *conn, gchar *msg, socket_conn_message_cb_t cb); + +static void *socket_conn_thread_fn(void *conn_closure) +{ + IdleConnection *conn = (IdleConnection *)(conn_closure); + IdleConnectionPrivate *priv; + GAsyncQueue *sendq = NULL; + TpConnectionStatusReason disconnect_reason = TP_CONN_STATUS_REASON_REQUESTED; + GError *error; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + sendq = g_async_queue_ref(priv->conn.out_queue); + + g_assert(sendq != NULL); + + if (!threaded_connection_open(conn, &error)) + { + g_debug("%s: threaded_connection_open failed: %s", G_STRFUNC, error->message); + + g_error_free(error); + + disconnect_reason = TP_CONN_STATUS_REASON_NETWORK_ERROR; + + goto exitl; + } + + while (!(priv->conn.exit)) + { + gchar *msg; + gboolean recv_ret; + + while ((recv_ret = receive_line(priv->conn.fd, &msg, priv->conn.fd)) && (msg != NULL)) + { + msg_split(conn, msg, priv->conn.message_cb); + free(msg); + } + + if (recv_ret == FALSE) + { + g_debug("%s: network error in receive_line", G_STRFUNC); + disconnect_reason = TP_CONN_STATUS_REASON_NETWORK_ERROR; + goto exitl; + } + + if (!priv->conn.queue_wait) + { + if (!send_all_queued(priv->conn.fd, priv->conn.out_queue)) + { + g_debug("%s: network error in send_all_queued", G_STRFUNC); + disconnect_reason = TP_CONN_STATUS_REASON_NETWORK_ERROR; + goto exitl; + } + } + else + { + g_debug("%s: queue holding", G_STRFUNC); + } + } + +exitl: + + g_async_queue_unref(sendq); + sendq = NULL; + +/* priv->conn.disconnected_cb(conn, disconnect_reason);*/ + + return NULL; +} + +static void msg_split(IdleConnection *conn, gchar *msg, socket_conn_message_cb_t cb) +{ + int i; + int lasti = 0; + gchar *tmp; + gboolean line_ends = FALSE; + IdleConnectionPrivate *priv; + guint len; + + g_assert(conn != NULL); + g_assert(msg != NULL); + g_assert(cb != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + len = strnlen(msg, IRC_MSG_MAXLEN+2); + + for (i = 0; i < len; i++) + { + if ((msg[i] == '\n' || msg[i] == '\r')) + { + msg[i] = '\0'; + + if (i>lasti) + { + if ((lasti == 0) && (priv->conn.msg_store != NULL)) + { + tmp = g_strconcat(priv->conn.msg_store, msg, NULL); + + g_free(priv->conn.msg_store); + priv->conn.msg_store = NULL; + } + else + { + tmp = g_strdup(msg+lasti); + } + + cb(conn, tmp); + + g_free(tmp); + } + + lasti = i+1; + + line_ends = TRUE; + } + else + { + line_ends = FALSE; + } + } + + if (!line_ends) + { + priv->conn.msg_store = g_strndup(msg+lasti, IRC_MSG_MAXLEN-lasti+2); + } +} + +static gboolean send_all_queued(int fd, GAsyncQueue *queue) +{ + gpointer data; + size_t length; + int i = 0; + + g_assert(fd != 0); + g_assert(queue != NULL); + + while ((data = g_async_queue_try_pop(queue))) + { + size_t written; + + i++; + + length = strnlen(data, IRC_MSG_MAXLEN+2); + + if ((written = send(fd, data, length, 0)) != length) + { + g_free(data); + + g_debug("%s: failed to send whole message (%u bytes written)", G_STRFUNC, written); + + return FALSE; + } + + g_free(data); + } + + return TRUE; +} + +static gboolean receive_line(int fd, gchar **msg, int poll_fd) +{ + gchar *ret_msg = NULL; + struct pollfd pfd = {poll_fd, POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL, 0}; + int poll_ret; + + g_assert(fd >= 0); + g_assert(msg != NULL); + g_assert(poll_fd >= 0); + + *msg = NULL; + + if ((poll_ret = poll(&pfd, 1, 500)) != 0) + { + if (poll_ret == -1) + { + g_debug("%s: poll() failed: %s", G_STRFUNC, strerror(errno)); + + return FALSE; + } + + if (pfd.revents & (POLLERR|POLLHUP|POLLNVAL)) + { + g_debug("%s: pfd.revents & (POLLERR|POLLHUP|POLLNVAL) != 0", G_STRFUNC); + + return FALSE; + } + + if (pfd.revents & (POLLIN|POLLPRI)) + { + size_t n = IRC_MSG_MAXLEN+2; + + ret_msg = g_malloc0(n); + + if ((n = recv(fd, ret_msg, n, 0)) <= 0) + { + g_debug("%s: recv() failed: %s", G_STRFUNC, strerror(errno)); + + g_free(ret_msg); + + return FALSE; + } + else + { + ret_msg[n] = '\0'; + } + } + } + else + { + *msg = NULL; + } + + *msg = ret_msg; + + return TRUE; +} +#endif + +static void connection_status_change(IdleConnection *conn, TpConnectionStatus status, TpConnectionStatusReason reason) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_debug("%s: status %u reason %u", G_STRFUNC, status, reason); + + if (priv->status != status) + { + priv->status = status; + + g_debug("%s: emitting status-changed with status %u reason %u", G_STRFUNC, status, reason); + g_signal_emit(conn, signals[STATUS_CHANGED], 0, status, reason); + } +} + +static void im_channel_closed_cb(IdleIMChannel *chan, gpointer user_data); +static void muc_channel_closed_cb(IdleMUCChannel *chan, gpointer user_data); + +gboolean hash_foreach_close_im_channel(gpointer key, gpointer value, gpointer user_data) +{ + IdleIMChannel *chan = IDLE_IM_CHANNEL(value); + GError *error = NULL; + + g_signal_handlers_disconnect_by_func(chan, (GCallback)(im_channel_closed_cb), user_data); + g_debug("%s: calling idle_im_channel_close on %p", G_STRFUNC, chan); + idle_im_channel_close(chan, &error); + g_debug("%s: removing channel %p", G_STRFUNC, chan); + return TRUE; +} + +gboolean hash_foreach_close_muc_channel(gpointer key, gpointer value, gpointer user_data) +{ + IdleMUCChannel *chan = IDLE_MUC_CHANNEL(value); + GError *error = NULL; + + g_signal_handlers_disconnect_by_func(chan, (GCallback)(muc_channel_closed_cb), user_data); + g_debug("%s: calling idle_muc_channel_close on %p", G_STRFUNC, chan); + idle_muc_channel_close(chan, &error); + g_debug("%s: removing channel %p", G_STRFUNC, chan); + return TRUE; +} + +static void close_all_channels(IdleConnection *conn) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (priv->im_channels) + { + GHashTable *tmp = priv->im_channels; + priv->im_channels = NULL; + g_hash_table_destroy(tmp); + } + + if (priv->muc_channels) + { + GHashTable *tmp = priv->muc_channels; + priv->muc_channels = NULL; + g_hash_table_destroy(tmp); + } +} + +static void send_quit_request(IdleConnection *conn) +{ + IdleConnectionPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+1]; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "QUIT :%s", priv->quit_message); + + send_irc_cmd(conn, cmd); +} + +static void connection_disconnect(IdleConnection *conn, TpConnectionStatusReason reason) +{ + IdleConnectionPrivate *priv; + GError *error; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + priv->disconnect_reason = reason; + + send_quit_request(conn); + + connection_status_change(conn, TP_CONN_STATUS_DISCONNECTED, reason); + + if (priv->conn != NULL) + { + if (!idle_server_connection_iface_disconnect(priv->conn, &error)) + { + g_debug("%s: server connection failed to disconnect: %s", G_STRFUNC, error->message); + } + } +} + +static void connection_disconnect_cb(IdleConnection *conn, TpConnectionStatusReason reason) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + g_debug("%s: called with reason %u", G_STRFUNC, reason); + + close_all_channels(conn); + connection_status_change(conn, TP_CONN_STATUS_DISCONNECTED, reason); + + if (priv->conn) + { + g_signal_handlers_disconnect_by_func(priv->conn, (GCallback)(sconn_status_changed_cb), conn); + g_object_unref(priv->conn); + priv->conn = NULL; + } + + if (priv->msg_queue_timeout) + { + g_source_remove(priv->msg_queue_timeout); + priv->msg_queue_timeout = 0; + } + + if (!priv->disconnected) + { + priv->disconnected = TRUE; + g_debug("%s: emitting DISCONNECTED", G_STRFUNC); + g_signal_emit(conn, signals[DISCONNECTED], 0); + } +} + +struct member_check_data +{ + IdleHandle handle; + gboolean is_present; +}; + +static void member_check_foreach(gpointer key, gpointer value, gpointer user_data) +{ + IdleMUCChannel *chan = IDLE_MUC_CHANNEL(value); + struct member_check_data *data = (struct member_check_data *)(user_data); + + if (!data->is_present && _idle_muc_channel_has_current_member(chan, data->handle)) + { + data->is_present = TRUE; + } +} + +const IdleContactPresence *get_contact_presence(IdleConnection *conn, IdleHandle handle) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + const IdleContactPresence *ret; + + ret = idle_handle_get_presence(priv->handles, handle); + + if (!ret) + { + const static IdleContactPresence available_presence = {IDLE_PRESENCE_AVAILABLE, NULL, 0}; + const static IdleContactPresence offline_presence = {IDLE_PRESENCE_OFFLINE, NULL, 0}; + + if (g_hash_table_lookup(priv->im_channels, GUINT_TO_POINTER(handle)) != NULL) + { + ret = &available_presence; + } + else + { + struct member_check_data data = {handle, FALSE}; + + g_hash_table_foreach(priv->muc_channels, member_check_foreach, &data); + + if (data.is_present) + { + ret = &available_presence; + } + else + { + ret = &offline_presence; + } + } + } + + return ret; +} + +static void +bastard_destroyer_from_collabora(GValue *value) +{ + g_value_unset(value); + g_free(value); +} + +static void emit_presence_update(IdleConnection *self, const IdleHandle *contact_handles) +{ + IdleConnectionPrivate *priv; + const IdleContactPresence *cp; + GHashTable *presence; + GValueArray *vals; + GHashTable *contact_status, *parameters; + int i; + + g_assert(self != NULL); + g_assert(IDLE_IS_CONNECTION(self)); + + priv = IDLE_CONNECTION_GET_PRIVATE(self); + + presence = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)(g_value_array_free)); + + for (i = 0; contact_handles[i] != 0; i++) + { + GValue *message; + + cp = get_contact_presence(self, contact_handles[i]); + + if (cp == NULL) + { + g_debug("%s: did not find presence for %u!!", G_STRFUNC, contact_handles[i]); + continue; + } + + message = g_new0(GValue, 1); + g_value_init(message, G_TYPE_STRING); + + g_value_set_string(message, cp->status_message); + + parameters = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)(bastard_destroyer_from_collabora)); + + g_hash_table_insert(parameters, "message", message); + + contact_status = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)(g_hash_table_destroy)); + + g_hash_table_insert(contact_status, (gpointer)(idle_statuses[cp->presence_state].name), parameters); + + vals = g_value_array_new(2); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 0), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 0), cp->last_activity); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 1), + dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))); + g_value_take_boxed(g_value_array_get_nth(vals, 1), contact_status); + + g_hash_table_insert(presence, GINT_TO_POINTER(contact_handles[i]), vals); + } + +#if 0 + for (i = 0; contact_handles[i] != 0; i++) + { + GValue *message; + + cp = idle_handle_get_qdata(priv->handles, TP_HANDLE_TYPE_CONTACT, contact_handles[i], data_key); + + if (!cp) + { + continue; + } + + message = g_new0(GValue, 1); + g_value_init(message, G_TYPE_STRING); + g_value_set_static_string(message, cp->status_message); + + parameters = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)(g_hash_table_destroy)); + + g_hash_table_insert(parameters, "message", message); + + contact_status = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)(g_hash_table_destroy)); + + g_hash_table_insert(contact_status, (gpointer)(idle_statuses[cp->presence_state].name), parameters); + + vals = g_value_array_new(2); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 0), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 0), timestamp); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 1), dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))); + g_value_take_boxed(g_value_array_get_nth(vals, 1), contact_status); + + g_hash_table_insert(presence, GINT_TO_POINTER(contact_handles[i]), vals); + } +#endif + g_debug("%s: emitting PRESENCE_UPDATE with %u presences", G_STRFUNC, g_hash_table_size(presence)); + g_signal_emit(self, signals[PRESENCE_UPDATE], 0, presence); + g_hash_table_destroy(presence); +} + +static gboolean signal_own_presence (IdleConnection *self, GError **error) +{ + IdleConnectionPrivate *priv; + gchar msg[IRC_MSG_MAXLEN+1]; + IdleContactPresence *cp; + + g_assert(self != NULL); + g_assert(error != NULL); + g_assert(IDLE_IS_CONNECTION(self)); + + priv = IDLE_CONNECTION_GET_PRIVATE(self); + + cp = idle_handle_get_presence(priv->handles, priv->self_handle); + + g_assert(cp != NULL); + + switch (cp->presence_state) + { + case IDLE_PRESENCE_AVAILABLE: + { + strcpy(msg, "AWAY"); + } + break; + default: + { + gchar *awaymsg; + + /* we can't post a zero-length away message so let's use "away" instead */ + if ((cp->status_message == NULL) || (strlen(cp->status_message) == 0)) + { + awaymsg = g_strdup("away"); + } + else + { + awaymsg = g_strndup(cp->status_message, IRC_MSG_MAXLEN+1-strlen("AWAY :")); + } + + sprintf(msg, "AWAY :%s", awaymsg); + + g_free(awaymsg); + } + break; + } + + send_irc_cmd(self, msg); + + return TRUE; +} + +static IdleIMChannel *new_im_channel(IdleConnection *conn, IdleHandle handle, gboolean suppress_handler) +{ + IdleConnectionPrivate *priv; + IdleIMChannel *chan; + gchar *object_path; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + object_path = g_strdup_printf("%s/ImChannel%u", priv->object_path, handle); + + chan = g_object_new(IDLE_TYPE_IM_CHANNEL, "connection", conn, + "object-path", object_path, + "handle", handle, + NULL); + + g_debug("%s: object path %s", G_STRFUNC, object_path); + + g_signal_connect(chan, "closed", (GCallback)(im_channel_closed_cb), conn); + + g_hash_table_insert(priv->im_channels, GINT_TO_POINTER(handle), chan); + + g_signal_emit(conn, signals[NEW_CHANNEL], 0, object_path, TP_IFACE_CHANNEL_TYPE_TEXT, + TP_HANDLE_TYPE_CONTACT, handle, suppress_handler); + + g_free(object_path); + + return chan; +} + +static IdleMUCChannel *new_muc_channel(IdleConnection *conn, IdleHandle handle, gboolean suppress_handler) +{ + IdleConnectionPrivate *priv; + IdleMUCChannel *chan; + gchar *object_path; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + object_path = g_strdup_printf("%s/MucChannel%u", priv->object_path, handle); + + chan = g_object_new(IDLE_TYPE_MUC_CHANNEL, "connection", conn, + "object-path", object_path, + "handle", handle, + NULL); + + g_debug("%s: object path %s", G_STRFUNC, object_path); + + g_signal_connect(chan, "closed", (GCallback)(muc_channel_closed_cb), conn); + + g_hash_table_insert(priv->muc_channels, GINT_TO_POINTER(handle), chan); + + g_signal_emit(conn, signals[NEW_CHANNEL], 0, object_path, TP_IFACE_CHANNEL_TYPE_TEXT, + TP_HANDLE_TYPE_ROOM, handle, suppress_handler); + + g_free(object_path); + + return chan; +} + +static void muc_channel_join_ready_cb(IdleMUCChannel *chan, guint err, gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + DBusGMethodInvocation *ctx; + gboolean suppress; + GError *error; + gchar *obj_path; + guint handle; + chan_req_data *data = g_hash_table_lookup(priv->chan_req_ctxs, chan); + + g_assert(data != NULL); + + ctx = data->ctx; + suppress = data->suppress_handler; + + g_object_get(chan, "handle", &handle, NULL); + g_object_get(chan, "object-path", &obj_path, NULL); + + switch (err) + { + case MUC_CHANNEL_JOIN_ERROR_NONE: + { + dbus_g_method_return(ctx, obj_path); + g_signal_emit(conn, signals[NEW_CHANNEL], 0, obj_path, TP_IFACE_CHANNEL_TYPE_TEXT, + TP_HANDLE_TYPE_ROOM, handle, suppress); + } + break; + case MUC_CHANNEL_JOIN_ERROR_BANNED: + { + error = g_error_new(TELEPATHY_ERRORS, ChannelBanned, "banned from room"); + dbus_g_method_return_error(ctx, error); + g_error_free(error); + } + break; + case MUC_CHANNEL_JOIN_ERROR_FULL: + { + error = g_error_new(TELEPATHY_ERRORS, ChannelFull, "room full"); + dbus_g_method_return_error(ctx, error); + g_error_free(error); + } + break; + case MUC_CHANNEL_JOIN_ERROR_INVITE_ONLY: + { + error = g_error_new(TELEPATHY_ERRORS, ChannelInviteOnly, "room invite only"); + dbus_g_method_return_error(ctx, error); + g_error_free(error); + } + break; + default: + g_assert_not_reached(); + } + + if (err != MUC_CHANNEL_JOIN_ERROR_NONE) + { + g_hash_table_remove(priv->muc_channels, GINT_TO_POINTER(handle)); + } + + g_hash_table_remove(priv->chan_req_ctxs, chan); + g_free(obj_path); +} + +static IdleMUCChannel *new_muc_channel_async_req(IdleConnection *conn, IdleHandle handle, gboolean suppress_handler, DBusGMethodInvocation *ctx) +{ + IdleConnectionPrivate *priv; + IdleMUCChannel *chan; + gchar *object_path; + chan_req_data *req_data; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + object_path = g_strdup_printf("%s/MucChannel%u", priv->object_path, handle); + + chan = g_object_new(IDLE_TYPE_MUC_CHANNEL, "connection", conn, + "object-path", object_path, + "handle", handle, + NULL); + + g_debug("%s: object path %s", G_STRFUNC, object_path); + + g_signal_connect(chan, "closed", (GCallback)(muc_channel_closed_cb), conn); + g_signal_connect(chan, "join-ready", (GCallback)(muc_channel_join_ready_cb), conn); + + g_hash_table_insert(priv->muc_channels, GINT_TO_POINTER(handle), chan); + + req_data = g_new0(chan_req_data, 1); + + req_data->ctx = ctx; + req_data->suppress_handler = suppress_handler; + + g_hash_table_insert(priv->chan_req_ctxs, chan, req_data); + + _idle_muc_channel_join_attempt(chan); + + g_free(object_path); + + return chan; +} + +static void im_channel_closed_cb(IdleIMChannel *chan, gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv; + IdleHandle contact_handle; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (priv->im_channels) + { + g_object_get(chan, "handle", &contact_handle, NULL); + + g_debug("%s: removing channel with handle %u", G_STRFUNC, contact_handle); + g_hash_table_remove(priv->im_channels, GINT_TO_POINTER(contact_handle)); + g_debug("%s: removed channel with handle %u", G_STRFUNC, contact_handle); + } +} + +static void muc_channel_closed_cb(IdleMUCChannel *chan, gpointer user_data) +{ + IdleConnection *conn = IDLE_CONNECTION(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + IdleHandle room_handle; + + if (priv->muc_channels) + { + g_object_get(chan, "handle", &room_handle, NULL); + + g_debug("%s: removing channel with handle %u", G_STRFUNC, room_handle); + g_hash_table_remove(priv->muc_channels, GINT_TO_POINTER(room_handle)); + g_debug("%s: removed channel with handle %u", G_STRFUNC, room_handle); + } +} + +static void destroy_handle_sets(gpointer set) +{ + IdleHandleSet *handle_set; + + g_assert(set != NULL); + handle_set = (IdleHandleSet *)(set); + + idle_handle_set_destroy(handle_set); +} + +void _idle_connection_client_hold_handle(IdleConnection *conn, gchar *client_name, + IdleHandle handle, TpHandleType type) +{ + IdleConnectionPrivate *priv; + IdleHandleSet *handle_set; + GData **handle_set_list; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + { + handle_set_list = &(priv->cl_contact_handle_sets); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + handle_set_list = &(priv->cl_room_handle_sets); + } + break; + default: + { + g_critical("%s: invalid handle type %u", G_STRFUNC, type); + return; + } + } + + handle_set = (IdleHandleSet *)(g_datalist_get_data(handle_set_list, client_name)); + + if (handle_set == NULL) + { + handle_set = idle_handle_set_new(priv->handles, type); + g_datalist_set_data_full(handle_set_list, client_name, handle_set, destroy_handle_sets); + } + + idle_handle_set_add(handle_set, handle); +} + +gboolean _idle_connection_client_release_handle(IdleConnection *conn, gchar *client_name, + IdleHandle handle, TpHandleType type) +{ + IdleConnectionPrivate *priv; + IdleHandleSet *handle_set; + GData **handle_set_list; + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + { + handle_set_list = &(priv->cl_contact_handle_sets); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + handle_set_list = &(priv->cl_room_handle_sets); + } + break; + default: + { + g_critical("%s: invalid handle type %u", G_STRFUNC, type); + return FALSE; + } + } + + handle_set = (IdleHandleSet *)(g_datalist_get_data(handle_set_list, client_name)); + + if (handle_set) + { + return idle_handle_set_remove(handle_set, handle); + } + else + { + return FALSE; + } +} + +static GHashTable * +get_statuses_arguments() +{ + static GHashTable *arguments = NULL; + + if (arguments == NULL) + { + arguments = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (arguments, "message", "s"); + } + + return arguments; +} + +#if 0 +/* slashcommand handlers */ + +static void _idle_connection_slashquery(IdleConnection *conn, const gchar *nicks); +static void _idle_connection_slashmsg(IdleConnection *conn, const gchar *nicknmsg, guint type); + +gboolean _idle_connection_slash_cmd(IdleConnection *conn, const gchar *cmd) +{ + IdleConnectionPrivate *priv; + + g_assert(conn != NULL); + g_assert(cmd != NULL); + + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + if (cmd[0] != '/') + { + return FALSE; + } + + if ((g_strncasecmp(cmd+1, "QUERY ", 6) == 0) && (cmd[7] != '\0')) + { + _idle_connection_slashquery(conn, cmd+7); + + return TRUE; + } + else if (g_strncasecmp(cmd+1, "MSG ", 4)) + { + _idle_connection_slashmsg(conn, cmd+5, TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL); + + return TRUE; + } + else if (g_strncasecmp(cmd+1, "NOTICE ", 7)) + { + _idle_connection_slashmsg(conn, cmd+8, TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE); + + return TRUE; + } + else if (g_strncasecmp(cmd+1, "ACTION ", 7)) + { + _idle_connection_slashmsg(conn, cmd+8, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION); + + return TRUE; + } + else + { + g_debug("%s: unimplemented slash command (%s)", G_STRFUNC, cmd); + + return FALSE; + } +} + +void _idle_connection_slashquery(IdleConnection *conn, const gchar *nicks) +{ + int i; + int lasti = 0; + gchar *nicktmp; + IdleConnectionPrivate *priv; + IdleIMChannel *chan; + IdleHandle handle; + size_t len = strlen(nicks); + + g_assert(conn != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + for (i=0; i < len; i++) + { + if (isblank(nicks[i])) + { + if (i > lasti) + { + nicktmp = g_malloc(i-lasti+1); + memcpy(nicktmp, nicks+lasti, i-lasti); + nicktmp[i-lasti+1] = '\0'; + + if ((handle = idle_handle_for_contact(priv->handles, nicktmp)) == 0) + { + g_debug("%s: invalid nick %s passed", G_STRFUNC, nicktmp); + } + else + { + chan = (IdleIMChannel *)(g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle))); + + if (chan != NULL) + { + g_debug("%s: query already open for %u", G_STRFUNC, handle); + } + else + { + g_debug("%s: spawning new IdleIMChannel for %u", G_STRFUNC, handle); + + chan = new_im_channel(conn, handle, FALSE); + } + } + + g_free(nicktmp); + } + + lasti = i+1; + } + } +} + +void _idle_connection_slashmsg(IdleConnection *conn, const gchar *nicknmsg, guint type) +{ + IdleConnectionPrivate *priv; + const gchar *nicktmp, *msgtmp; + gchar *nickcopy; + + g_assert(conn != NULL); + g_assert(nicknmsg != NULL); + g_assert(IDLE_IS_CONNECTION(conn)); + + priv = IDLE_CONNECTION_GET_PRIVATE(conn); + + for (nicktmp = nicknmsg; isblank(*nicktmp); nicktmp++){} + + for (msgtmp = nicktmp+1; (!isblank(*msgtmp)) && (*msgtmp != '\0'); msgtmp++){} + + nickcopy = g_strndup(nicktmp, msgtmp-nicktmp-1); + + for (msgtmp++; isblank(*msgtmp); msgtmp++){} + + if (isalpha(nicktmp[0])) + { + IdleHandle handle; + IdleIMChannel *chan; + + if ((handle = idle_handle_for_contact(priv->handles, nickcopy)) == 0) + { + g_debug("%s: invalid nick %s", G_STRFUNC, nicktmp); + } + else + { + chan = (IdleIMChannel *)(g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle))); + + if (chan == NULL) + { + char *msg; + + g_debug("%s: sending to %s straight without a channel", G_STRFUNC, nickcopy); + + switch (type) + { + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL: + { + msg = g_strdup_printf("PRIVMSG %s :%s", nickcopy, msgtmp); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE: + { + msg = g_strdup_printf("NOTICE %s :%s", nickcopy, msgtmp); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION: + { + msg = g_strdup_printf("PRIVMSG %s :\001ACTION %s\001", nickcopy, msgtmp); + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + + send_irc_cmd(conn, msg); + + g_free(msg); + } + else + { + GError *send_error = NULL; + + idle_im_channel_send(chan, type, msgtmp, &send_error); + + if (send_error) + { + g_debug("%s: error in idle_im_channel_send: %s", G_STRFUNC, send_error->message); + + g_error_free(send_error); + } + } + } + } + else + { + switch (nicktmp[0]) + { + case '&': + case '#': + case '+': + case '!': + { + IdleHandle handle; + + if ((handle = idle_handle_for_room(priv->handles, nickcopy)) == 0) + { + g_debug("%s: failed to get handle for channel %s", G_STRFUNC, nickcopy); + } + else + { + IdleMUCChannel *chan; + GError *send_error = NULL; + + chan = (IdleMUCChannel *)(g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle))); + + if (chan == NULL) + { + gchar *msg; + + g_debug("%s: sending straight to %s without a channel", G_STRFUNC, nickcopy); + + switch (type) + { + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL: + { + msg = g_strdup_printf("PRIVMSG %s :%s", nickcopy, msgtmp); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE: + { + msg = g_strdup_printf("NOTICE %s :%s", nickcopy, msgtmp); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION: + { + msg = g_strdup_printf("PRIVMSG %s :\001ACTION %s\001", nickcopy, msgtmp); + } + break; + default: + { + g_assert_not_reached(); + } + break; + } + + send_irc_cmd(conn, msg); + + g_free(msg); + } + else + { + idle_muc_channel_send(chan, type, msgtmp, &send_error); + + if (send_error != NULL) + { + g_debug("%s: idle_muc_channel_send failed: %s", G_STRFUNC, send_error->message); + + g_error_free(send_error); + } + } + } + } + break; + default: + { + g_debug("%s: ignored invalid target %s for /msg, /action or /notice", G_STRFUNC, nickcopy); + } + } + } + + g_free(nickcopy); +} +#endif +/* D-BUS-exported methods */ + +/** + * idle_connection_add_status + * + * Implements DBus method AddStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_add_status (IdleConnection *obj, const gchar * status, GHashTable * parms, GError **error) +{ + IdleConnectionPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + *error = g_error_new(TELEPATHY_ERRORS, NotImplemented, + "Only one status is possible at a time with this protocol"); + + return FALSE; +} + + +#if 0 +/** + * idle_connection_advertise_capabilities + * + * Implements DBus method AdvertiseCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_advertise_capabilities (IdleConnection *obj, const gchar ** add, const gchar ** remove, GError **error) +{ + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + ERROR_IF_NOT_CONNECTED(obj, IDLE_CONNECTION_GET_PRIVATE(obj), *error); + + /* collabora doesn't implement these -> I don't implement these . . . . */ + + return TRUE; +} +#endif + + +/** + * idle_connection_clear_status + * + * Implements DBus method ClearStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_clear_status (IdleConnection *obj, GError **error) +{ + IdleConnectionPrivate *priv; + IdleContactPresence *cp; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + cp = idle_handle_get_presence(priv->handles, priv->self_handle); + + if (!cp) + { + return TRUE; + } + + cp->presence_state = IDLE_PRESENCE_AVAILABLE; + g_free(cp->status_message); + cp->status_message = NULL; + cp->last_activity = 0; + + return signal_own_presence(obj, error); +} + + +/** + * idle_connection_connect + * + * Implements DBus method Connect + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_connect (IdleConnection *obj, GError **error) +{ + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + return _idle_connection_connect(obj, error); +} + + +/** + * idle_connection_disconnect + * + * Implements DBus method Disconnect + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_disconnect (IdleConnection *obj, GError **error) +{ + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + connection_disconnect(obj, TP_CONN_STATUS_REASON_REQUESTED); + + return TRUE; +} + + +/* bah, capabilites is no good atm */ +#if 0 +/** + * idle_connection_get_capabilities + * + * Implements DBus method GetCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_capabilities (IdleConnection *obj, GArray *handles, GPtrArray ** ret, GError **error) +{ + int i; + IdleConnectionPrivate *priv; + IdleHandle handle; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + *ret = g_ptr_array_sized_new(handles->len); + + for (i = 0; i<handles->len; i++) + { + GValue vals = {0.}; + + handle = g_array_index(handles, guint, i); + + g_value_init(&vals, TP_CAPABILITY_PAIR_TYPE); + g_value_set_static_boxed(&vals, dbus_g_type_specialized_construct(TP_CAPABILITY_PAIR_TYPE)); + + if (handle == 0) + { + dbus_g_type_struct_set(&vals, + 0, handle, + } + else if (!idle_handle_is_valid(priv->handles, TP_HANDLE_TYPE_CONTACT, handle) && !idle_handle_is_valid(priv->handles, TP_HANDLE_TYPE_ROOM, handle)) + { + g_debug("%s: invalid handle %u", G_STRFUNC, handle); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle %u", handle); + + return FALSE; + } + + *ret = g_ptr_array_sized_new(2); + + g_value_init(&vals, TP_CAPABILITY_PAIR_TYPE); + g_value_set_static_boxed(&vals, dbus_g_type_specialized_construct(TP_CAPABILITY_PAIR_TYPE)); + + dbus_g_type_struct_set(&vals, 0, TP_IFACE_CHANNEL_TYPE_TEXT, 1, TP_CONN_CAPABILITY_TYPE_CREATE, G_MAXUINT); + + g_ptr_array_add(*ret, g_value_get_boxed(&vals)); + + dbus_g_type_struct_set(&vals, 0, TP_IFACE_CHANNEL_TYPE_TEXT, 1, TP_CONN_CAPABILITY_TYPE_CREATE, G_MAXUINT); + + return TRUE; +} +#endif + +/** + * idle_connection_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_interfaces (IdleConnection *obj, gchar *** ret, GError **error) +{ + const char *interfaces[] = {TP_IFACE_CONN_INTERFACE_PRESENCE, + /*TP_IFACE_CONN_INTERFACE_CAPABILITIES,*/ + TP_IFACE_CONN_INTERFACE_RENAMING, + NULL}; + IdleConnectionPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + *ret = g_strdupv((gchar**)(interfaces)); + + return TRUE; +} + + +/** + * idle_connection_get_protocol + * + * Implements DBus method GetProtocol + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_protocol (IdleConnection *obj, gchar ** ret, GError **error) +{ + IdleConnectionPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + *ret = g_strdup(priv->protocol); + + return TRUE; +} + + +/** + * idle_connection_get_self_handle + * + * Implements DBus method GetSelfHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_self_handle (IdleConnection *obj, guint* ret, GError **error) +{ + IdleConnectionPrivate *priv; + + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + *ret = priv->self_handle; + + return TRUE; +} + + +/** + * idle_connection_get_status + * + * Implements DBus method GetStatus + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_status (IdleConnection *obj, guint* ret, GError **error) +{ + IdleConnectionPrivate *priv; + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + *ret = priv->status; + + return TRUE; +} + + +/** + * idle_connection_get_statuses + * + * Implements DBus method GetStatuses + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_get_statuses (IdleConnection *obj, GHashTable ** ret, GError **error) +{ + IdleConnectionPrivate *priv; + GValueArray *status; + int i; + + g_assert (IDLE_IS_CONNECTION (obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE (obj); + + ERROR_IF_NOT_CONNECTED (obj, priv, *error) + + g_debug ("%s called.", G_STRFUNC); + + *ret = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) g_value_array_free); + + for (i=0; i < LAST_IDLE_PRESENCE_ENUM; i++) + { + status = g_value_array_new (5); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 0), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (status, 0), + idle_statuses[i].presence_type); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth(status, 1), G_TYPE_BOOLEAN); + g_value_set_boolean(g_value_array_get_nth(status, 1), + idle_statuses[i].self); + + g_value_array_append (status, NULL); + g_value_init(g_value_array_get_nth (status, 2), G_TYPE_BOOLEAN); + g_value_set_boolean(g_value_array_get_nth (status, 2), + idle_statuses[i].exclusive); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 3), + DBUS_TYPE_G_STRING_STRING_HASHTABLE); + g_value_set_static_boxed(g_value_array_get_nth (status, 3), + get_statuses_arguments()); + + g_hash_table_insert (*ret, (gchar*)(idle_statuses[i].name), status); + } + + return TRUE; +} + + +/** + * idle_connection_hold_handles + * + * Implements DBus method HoldHandles + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_hold_handles (IdleConnection *obj, + guint handle_type, + const GArray *handles, + DBusGMethodInvocation *context) +{ + IdleConnectionPrivate *priv; + GError *error = NULL; + gchar *sender; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED_ASYNC(obj, priv, error, context); + + if (!idle_handle_type_is_valid(handle_type)) + { + g_debug("%s: invalid handle type %u", G_STRFUNC, handle_type); + + error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle type %u", handle_type); + dbus_g_method_return_error(context, error); + g_error_free(error); + + return FALSE; + } + + sender = dbus_g_method_get_sender(context); + + for (i=0; i<handles->len; i++) + { + IdleHandle handle; + gboolean valid; + + handle = g_array_index(handles, guint, i); + valid = idle_handle_is_valid(priv->handles, handle_type, handle); + + if (!valid) + { + g_debug("%s: invalid handle %u", G_STRFUNC, handle); + + error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u", handle); + + dbus_g_method_return_error(context, error); + + g_error_free(error); + + return FALSE; + } + + _idle_connection_client_hold_handle(obj, sender, handle, handle_type); + } + + dbus_g_method_return(context); + + return TRUE; +} + + +/** + * idle_connection_inspect_handle + * + * Implements DBus method InspectHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_inspect_handle (IdleConnection *obj, guint handle_type, guint handle, gchar ** ret, GError **_error) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(obj); + const gchar *tmp; + + ERROR_IF_NOT_CONNECTED(obj, priv, *_error); + + if (!idle_handle_type_is_valid(handle_type)) + { + g_debug("%s: invalid handle type %u", G_STRFUNC, handle_type); + + *_error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle type %u", handle_type); + + return FALSE; + } + + tmp = idle_handle_inspect(priv->handles, handle_type, handle); + + if (tmp == NULL) + { + g_debug("%s: invalid handle %u (type %u)", G_STRFUNC, handle, handle_type); + + *_error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u", handle); + + return FALSE; + } + + *ret = g_strdup(tmp); + + return TRUE; +} + +gboolean idle_connection_inspect_handles (IdleConnection *conn, guint handle_type, const GArray *handles, DBusGMethodInvocation *ctx) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + const char *tmp; + const gchar **ret; + int i; + GError *error; + + ERROR_IF_NOT_CONNECTED_ASYNC(obj, priv, error, ctx); + + if (!idle_handle_type_is_valid(handle_type)) + { + g_debug("%s: invalid handle type %u", G_STRFUNC, handle_type); + + error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle type %u", handle_type); + + dbus_g_method_return_error(ctx, error); + + return FALSE; + } + + ret = g_new(const gchar *, handles->len+1); + + for (i=0; i<handles->len; i++) + { + IdleHandle handle = g_array_index(handles, guint, i); + + tmp = idle_handle_inspect(priv->handles, handle_type, handle); + + if (tmp == NULL) + { + g_debug("%s: invalid handle %u (type %u)", G_STRFUNC, handle, handle_type); + + error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u", handle); + + g_free(ret); + + dbus_g_method_return_error(ctx, error); + + return FALSE; + } + + ret[i] = tmp; + } + + ret[i] = NULL; + + dbus_g_method_return(ctx, ret); + + g_free(ret); + + return TRUE; +} + +/** + * list_channel_hash_foreach: + * @key: iterated key + * @value: iterated value + * @data: data attached to this key/value pair + * + * Called by the exported ListChannels function, this should iterate over + * the handle/channel pairs in a hash, and to the GPtrArray in the + * ListChannelInfo struct, add a GValueArray containing the following: + * a D-Bus object path for the channel object on this service + * a D-Bus interface name representing the channel type + * an integer representing the handle type this channel communicates with, or zero + * an integer handle representing the contact, room or list this channel communicates with, or zero + */ +static void +list_channel_hash_foreach (gpointer key, + gpointer value, + gpointer data) +{ + GObject *channel = G_OBJECT (value); + GPtrArray *channels = (GPtrArray *) data; + char *path, *type; + guint handle_type, handle; + GValueArray *vals; + + g_object_get (channel, "object-path", &path, + "channel-type", &type, + "handle-type", &handle_type, + "handle", &handle, NULL); + + g_debug ("list_channels_foreach_hash: adding path %s, type %s, " + "handle type %u, handle %u", path, type, handle_type, handle); + + vals = g_value_array_new (4); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 0), DBUS_TYPE_G_OBJECT_PATH); + g_value_set_boxed (g_value_array_get_nth (vals, 0), path); + g_free (path); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 1), G_TYPE_STRING); + g_value_set_string (g_value_array_get_nth (vals, 1), type); + g_free (type); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 2), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 2), handle_type); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 3), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 3), handle); + + g_ptr_array_add (channels, vals); +} + +/** + * idle_connection_list_channels + * + * Implements DBus method ListChannels + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_list_channels (IdleConnection *obj, GPtrArray ** ret, GError **error) +{ + IdleConnectionPrivate *priv; + guint count; + GPtrArray *channels; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + count = g_hash_table_size(priv->im_channels); + count += g_hash_table_size(priv->muc_channels); + channels = g_ptr_array_sized_new(count); + + g_hash_table_foreach(priv->im_channels, list_channel_hash_foreach, channels); + g_hash_table_foreach(priv->muc_channels, list_channel_hash_foreach, channels); + + *ret = channels; + + return TRUE; +} + + +/** + * idle_connection_release_handle + * + * Implements DBus method ReleaseHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_release_handles (IdleConnection *obj, + guint handle_type, + const GArray *handles, + DBusGMethodInvocation *context) +{ + IdleConnectionPrivate *priv; + char *sender; + GError *error = NULL; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED_ASYNC(obj, priv, error, context); + + if (!idle_handle_type_is_valid(handle_type)) + { + g_debug("%s: invalid handle type %u", G_STRFUNC, handle_type); + + error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle type %u", handle_type); + + dbus_g_method_return_error(context, error); + + g_error_free(error); + + return FALSE; + } + + sender = dbus_g_method_get_sender(context); + + for (i=0; i<handles->len; i++) + { + IdleHandle handle; + gboolean valid; + + handle = g_array_index(handles, guint, i); + valid = idle_handle_is_valid(priv->handles, handle_type, handle); + + if (!valid) + { + g_debug("%s: invalid handle %u", G_STRFUNC, handle); + + error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "unknown handle %u", handle); + + dbus_g_method_return_error(context, error); + + g_error_free(error); + + return FALSE; + } + + _idle_connection_client_release_handle(obj, sender, handle, handle_type); + } + + dbus_g_method_return(context); + + return TRUE; +} + + +/** + * idle_connection_remove_status + * + * Implements DBus method RemoveStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_remove_status (IdleConnection *obj, const gchar * status, GError **error) +{ + IdleConnectionPrivate *priv; + IdleContactPresence *cp; + + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + cp = idle_handle_get_presence(priv->handles, priv->self_handle); + + if ((cp != NULL) && !strcmp(status, idle_statuses[cp->presence_state].name)) + { + cp->presence_state = IDLE_PRESENCE_AVAILABLE; + g_free(cp->status_message); + cp->status_message = NULL; + cp->last_activity = 0; + return signal_own_presence(obj, error); + } + else + { + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "Attempting to remove non-existant presence."); + return FALSE; + } +} + +/** + * idle_connection_request_channel + * + * Implements DBus method RequestChannel + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_request_channel (IdleConnection *obj, const gchar * type, guint handle_type, guint handle, gboolean suppress_handler, DBusGMethodInvocation *ctx) +{ + IdleConnectionPrivate *priv; + GError *error = NULL; + gboolean queued = FALSE; + gchar *ret; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED_ASYNC(obj, priv, error, ctx); + + if (!idle_handle_is_valid(priv->handles, handle_type, handle)) + { + goto INVALID_HANDLE; + } + + if (!strcmp(type, TP_IFACE_CHANNEL_TYPE_TEXT)) + { + gpointer chan; + + switch (handle_type) + { + case TP_HANDLE_TYPE_CONTACT: + { + chan = g_hash_table_lookup(priv->im_channels, GINT_TO_POINTER(handle)); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + chan = g_hash_table_lookup(priv->muc_channels, GINT_TO_POINTER(handle)); + } + break; + default: + { + goto NOT_AVAILABLE; + } + break; + } + + if (chan == NULL) + { + switch (handle_type) + { + case TP_HANDLE_TYPE_CONTACT: + { + chan = new_im_channel(obj, handle, suppress_handler); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + queued = TRUE; + chan = new_muc_channel_async_req(obj, handle, suppress_handler, ctx); + } + break; + default: + { + goto NOT_AVAILABLE; + } + break; + } + } + + g_object_get(chan, "object-path", &ret, NULL); + } + else + { + goto NOT_IMPLEMENTED; + } + + if (!queued) + { + dbus_g_method_return(ctx, ret); + } + + g_free(ret); + + return TRUE; + +NOT_AVAILABLE: + g_debug ("request_channel: requested channel is unavailable with " + "handle type %u", handle_type); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "requested channel is not available with " + "handle type %u", handle_type); + dbus_g_method_return_error(ctx, error); + g_free(error); + + return FALSE; + +INVALID_HANDLE: + g_debug ("request_channel: handle %u (type %u) not valid", handle, handle_type); + + error = g_error_new (TELEPATHY_ERRORS, InvalidHandle, + "handle %u (type %u) not valid", handle, handle_type); + dbus_g_method_return_error(ctx, error); + g_free(error); + + return FALSE; + +NOT_IMPLEMENTED: + g_debug ("request_channel: unsupported channel type %s", type); + + error = g_error_new (TELEPATHY_ERRORS, NotImplemented, + "unsupported channel type %s", type); + dbus_g_method_return_error(ctx, error); + g_free(error); + + return FALSE; +} + +static IdleHandle _idle_connection_request_handle(IdleConnection *obj, + guint handle_type, + const gchar *name, + GError **error); + +/** + * idle_connection_request_handles + * + * Implements DBus method RequestHandles + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The DBUS invocation context to use to return values + * or throw an error. + */ +gboolean idle_connection_request_handles (IdleConnection *obj, + guint handle_type, + const gchar **names, + DBusGMethodInvocation *context) +{ + IdleConnectionPrivate *priv; + gchar *sender; + GError *error = NULL; + GArray *handles; + const gchar **name; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED_ASYNC(obj, priv, error, context); + + if (!idle_handle_type_is_valid(handle_type)) + { + g_debug("%s: invalid handle type %u", G_STRFUNC, handle_type); + + error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid handle type %u", handle_type); + dbus_g_method_return_error(context, error); + g_error_free(error); + + return FALSE; + } + + sender = dbus_g_method_get_sender(context); + handles = g_array_new(FALSE, FALSE, sizeof(IdleHandle)); + + for (name = names; *name != NULL; name++) + { + IdleHandle handle; + + handle = _idle_connection_request_handle(obj, handle_type, *name, &error); + + if (!handle) + { + g_debug("%s: failed to request handle: %s", G_STRFUNC, error->message); + g_array_free(handles, TRUE); + + dbus_g_method_return_error(context, error); + g_error_free(error); + + return FALSE; + } + + _idle_connection_client_hold_handle(obj, sender, handle, handle_type); + + g_array_append_val(handles, handle); + } + + dbus_g_method_return(context, handles); + g_array_free(handles, TRUE); + + return TRUE; +} + +static IdleHandle _idle_connection_request_handle(IdleConnection *obj, + guint handle_type, + const gchar *name, + GError **error) +{ + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(obj); + IdleHandle handle; + gchar *final_name; + + switch (handle_type) + { + case TP_HANDLE_TYPE_CONTACT: + { + handle = idle_handle_for_contact(priv->handles, name); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + switch (name[0]) + { + case '#': + case '&': + case '!': + case '+': + { + final_name = (gchar *)(name); + } + break; + default: + { + g_debug("%s: assuming user wanted #-channel (%s)", G_STRFUNC, name); + final_name = g_strdup_printf("#%s", name); + } + break; + } + + handle = idle_handle_for_room(priv->handles, final_name); + + if (final_name != name) + { + g_free(final_name); + } + } + break; + default: + { + g_debug("%s: unimplemented handle type %u", G_STRFUNC, handle_type); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "unimplemented handle type %u", handle_type); + return 0; + } + break; + } + + if (handle == 0) + { + g_debug("%s: requested name %s was invalid", G_STRFUNC, name); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "requested name %s was invalid", name); + + return 0; + } + + return handle; +} + +static gboolean presence_timer_cb(gpointer data) +{ + IdleConnection *conn = IDLE_CONNECTION(data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(conn); + gchar *nick; + gchar cmd[IRC_MSG_MAXLEN+2]; + + nick = g_queue_pop_head(priv->presence_queue); + + if (nick == NULL) + { + priv->presence_unload_timer_id = 0; + + return FALSE; + } + + g_snprintf(cmd, IRC_MSG_MAXLEN+2, "WHOIS %s", nick); + + send_irc_cmd_full(conn, cmd, SERVER_CMD_MIN_PRIORITY); + + priv->presence_reply_list = g_list_append(priv->presence_reply_list, nick); + + return TRUE; +} + +#define PRESENCE_TIMER_TIMEOUT 3300 + +static void presence_request_push(IdleConnection *obj, const gchar *nick) +{ + IdleConnectionPrivate *priv; + GList *node; + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + node = g_queue_find_custom(priv->presence_queue, nick, (GCompareFunc)(strcmp)); + + if (!node) + { + g_queue_push_tail(priv->presence_queue, g_strdup(nick)); + } + + if (priv->presence_unload_timer_id == 0) + { + priv->presence_unload_timer_id = g_timeout_add(PRESENCE_TIMER_TIMEOUT, presence_timer_cb, obj); + } +} + +/** + * idle_connection_request_presence + * + * Implements DBus method RequestPresence + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_request_presence (IdleConnection *obj, const GArray * contacts, GError **error) +{ + IdleConnectionPrivate *priv; + int i; + IdleHandle *handles = g_new0(IdleHandle, contacts->len+1); + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + for (i=0; i<contacts->len; i++) + { + IdleHandle handle; + + handle = g_array_index(contacts, guint, i); + + if (!idle_handle_is_valid(priv->handles, TP_HANDLE_TYPE_CONTACT, handle)) + { + g_debug("%s: invalid handle %u", G_STRFUNC, handle); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u", handle); + + return FALSE; + } + + handles[i] = handle; + } + + handles[i] = 0; + + g_debug("%s: got presence request for %u handles", G_STRFUNC, contacts->len); + + if (contacts->len) + { + emit_presence_update(obj, handles); + } + + g_free(handles); + + return TRUE; +} + + +/** + * idle_connection_set_last_activity_time + * + * Implements DBus method SetLastActivityTime + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_set_last_activity_time (IdleConnection *obj, guint time, GError **error) +{ + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + ERROR_IF_NOT_CONNECTED(obj, IDLE_CONNECTION_GET_PRIVATE(obj), *error); + + /* this is not really possible but let's pretend it succeeded */ + + return TRUE; +} + +struct idle_conn_hashtable_foreach_data +{ + IdleConnection *conn; + GError **error; + gboolean retval; +}; + +static void setstatuses_foreach(gpointer key, gpointer value, gpointer user_data) +{ + struct idle_conn_hashtable_foreach_data *data = (struct idle_conn_hashtable_foreach_data *)(user_data); + IdleConnectionPrivate *priv = IDLE_CONNECTION_GET_PRIVATE(data->conn); + int i; + + for (i = 0; i < LAST_IDLE_PRESENCE_ENUM; i++) + { + if (!strcmp(idle_statuses[i].name, (const gchar *)(key))) + { + break; + } + } + + if (i < LAST_IDLE_PRESENCE_ENUM) + { + GHashTable *args = (GHashTable *)(value); + GValue *message = g_hash_table_lookup(args, "message"); + const gchar *status = NULL; + IdleContactPresence *cp; + + if (message) + { + if (!G_VALUE_HOLDS_STRING(message)) + { + g_debug("%s: got a status message which was not a string", G_STRFUNC); + + if (*(data->error)) + { + g_error_free(*(data->error)); + *(data->error) = NULL; + } + + *(data->error) = g_error_new(TELEPATHY_ERRORS, InvalidArgument, + "Status argument 'message' requires a string"); + data->retval = FALSE; + return; + } + + status = g_value_get_string(message); + } + + cp = idle_handle_get_presence(priv->handles, priv->self_handle); + + if (!cp) + { + cp = idle_contact_presence_new(); + g_assert(idle_handle_set_presence(priv->handles, priv->self_handle, cp)); + } + + cp->presence_state = i; + cp->status_message = g_strdup(status); + cp->last_activity = 0; + + data->retval = signal_own_presence(data->conn, data->error); + } + else + { + g_debug("%s: got unknown status identifier %s", G_STRFUNC, (const gchar *)(key)); + + if (*(data->error)) + { + g_error_free(*(data->error)); + *(data->error) = NULL; + } + + *(data->error) = g_error_new(TELEPATHY_ERRORS, InvalidArgument, + "unknown status identifier received: %s", + (const gchar *)(key)); + data->retval = FALSE; + } +} + +/** + * idle_connection_set_status + * + * Implements DBus method SetStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_connection_set_status (IdleConnection *obj, GHashTable * statuses, GError **error) +{ + IdleConnectionPrivate *priv; + int size; + struct idle_conn_hashtable_foreach_data data = {obj, error, TRUE}; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + ERROR_IF_NOT_CONNECTED(obj, priv, *error); + + if ((size = g_hash_table_size(statuses)) != 1) + { + g_debug("%s: got %i statuses instead of 1", G_STRFUNC, size); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "Got %i statuses instead of 1", size); + + return FALSE; + } + + g_hash_table_foreach(statuses, setstatuses_foreach, &data); + + if (*(data.error) != NULL) + { + g_debug("%s: error: %s", G_STRFUNC, (*data.error)->message); + g_error_free(*(data.error)); + } + + return data.retval; +} + +gboolean idle_connection_request_rename(IdleConnection *obj, const gchar *nick, GError **error) +{ + IdleConnectionPrivate *priv; + IdleHandle handle; + + g_assert(obj != NULL); + g_assert(IDLE_IS_CONNECTION(obj)); + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + handle = idle_handle_for_contact(priv->handles, nick); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s)", G_STRFUNC, nick); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "Failed to get handle for (%s)", nick); + + return FALSE; + } + + g_free(priv->nickname); + + priv->nickname = g_strdup(nick); + + priv_rename(obj, priv->self_handle, handle); + + return TRUE; +} + +gboolean idle_connection_hton(IdleConnection *obj, const gchar *input, gchar **output, GError **_error) +{ + IdleConnectionPrivate *priv; + GError *error = NULL; + gsize bytes_written; + gchar *ret; + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + if (input == NULL) + { + *output = NULL; + return TRUE; + } + + ret = g_convert(input, -1, priv->charset, "UTF-8", NULL, &bytes_written, &error); + + if (ret == NULL) + { + g_debug("%s: g_convert failed: %s", G_STRFUNC, error->message); + *_error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "character set conversion failed: %s", error->message); + g_error_free(error); + *output = NULL; + return FALSE; + } + + *output = ret; + return TRUE; +} + +gboolean idle_connection_ntoh(IdleConnection *obj, const gchar *input, gchar **output, GError **_error) +{ + IdleConnectionPrivate *priv; + GError *error = NULL; + gsize bytes_written; + gchar *ret; + gchar *p; + + priv = IDLE_CONNECTION_GET_PRIVATE(obj); + + if (input == NULL) + { + *output = NULL; + return TRUE; + } + + ret = g_convert(input, -1, "UTF-8", priv->charset, NULL, &bytes_written, &error); + +#if 0 + if (ret == NULL) + { + g_debug("%s: g_convert failed: %s", G_STRFUNC, error->message); + *_error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "character set conversion failed: %s", error->message); + g_error_free(error); + *output = NULL; + return FALSE; + } +#endif + + if (ret == NULL) + { + g_debug("%s: charset conversion failed, falling back to US-ASCII: %s", G_STRFUNC, error->message); + g_error_free(error); + + ret = g_strdup(input); + + for (p=ret; *p != '\0'; p++) + { + if (*p & (1<<7)) + { + *p = '?'; + } + } + } + + *output = ret; + return TRUE; +} diff --git a/src/idle-connection.h b/src/idle-connection.h new file mode 100644 index 0000000..820b77c --- /dev/null +++ b/src/idle-connection.h @@ -0,0 +1,106 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_CONNECTION_H__ +#define __IDLE_CONNECTION_H__ + +#include <glib-object.h> +#define DBUS_API_SUBJECT_TO_CHANGE +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +typedef struct _IdleConnection IdleConnection; +typedef struct _IdleConnectionClass IdleConnectionClass; +typedef struct _IdleContactPresence IdleContactPresence; + +#include "idle-handles.h" +#include "telepathy-constants.h" + +G_BEGIN_DECLS + +typedef enum +{ + IDLE_PRESENCE_AVAILABLE, + IDLE_PRESENCE_AWAY, + IDLE_PRESENCE_OFFLINE, + LAST_IDLE_PRESENCE_ENUM, +} IdlePresenceState; + +struct _IdleConnectionClass +{ + GObjectClass parent_class; +}; + +struct _IdleConnection +{ + GObject parent; +}; + +void idle_contact_presence_free(IdleContactPresence *cp); + +GType idle_connection_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_CONNECTION \ + (idle_connection_get_type()) +#define IDLE_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_CONNECTION, IdleConnection)) +#define IDLE_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_CONNECTION, IdleConnectionClass)) +#define IDLE_IS_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_CONNECTION)) +#define IDLE_IS_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_CONNECTION)) +#define IDLE_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_CONNECTION, IdleConnectionClass)) + +gboolean _idle_connection_register(IdleConnection *conn, char **bus_name, char **object_path, GError **error); +gboolean _idle_connection_send(IdleConnection *conn, const gchar *msg); +IdleHandleStorage *_idle_connection_get_handles(IdleConnection *conn); +void _idle_connection_client_hold_handle(IdleConnection *conn, gchar *client_name, IdleHandle handle, TpHandleType type); +gboolean _idle_connection_client_release_handle(IdleConnection *conn, gchar *client_name, IdleHandle handle, TpHandleType type); + +gboolean idle_connection_add_status (IdleConnection *obj, const gchar *status, GHashTable *parms, GError **error); +gboolean idle_connection_clear_status (IdleConnection *obj, GError **error); +gboolean idle_connection_connect (IdleConnection *obj, GError **error); +gboolean idle_connection_disconnect (IdleConnection *obj, GError **error); +gboolean idle_connection_get_interfaces (IdleConnection *obj, gchar ***ret, GError **error); +gboolean idle_connection_get_protocol (IdleConnection *obj, gchar **ret, GError **error); +gboolean idle_connection_get_self_handle (IdleConnection *obj, guint*ret, GError **error); +gboolean idle_connection_get_status (IdleConnection *obj, guint*ret, GError **error); +gboolean idle_connection_get_statuses (IdleConnection *obj, GHashTable **ret, GError **error); +gboolean idle_connection_hold_handles (IdleConnection *obj, guint handle_type, const GArray *handles, DBusGMethodInvocation *context); +gboolean idle_connection_inspect_handles (IdleConnection *obj, guint handle_type, const GArray *handles, DBusGMethodInvocation *context); +gboolean idle_connection_list_channels (IdleConnection *obj, GPtrArray **ret, GError **error); +gboolean idle_connection_release_handles (IdleConnection *obj, guint handle_type, const GArray *handles, DBusGMethodInvocation *context); +gboolean idle_connection_remove_status (IdleConnection *obj, const gchar *status, GError **error); +gboolean idle_connection_request_channel (IdleConnection *obj, const gchar *type, guint handle_type, guint handle, gboolean suppress_handler, DBusGMethodInvocation *ctx); +gboolean idle_connection_request_handles (IdleConnection *obj, guint handle_type, const gchar **names, DBusGMethodInvocation *context); +gboolean idle_connection_request_presence (IdleConnection *obj, const GArray *contacts, GError **error); +gboolean idle_connection_set_last_activity_time (IdleConnection *obj, guint time, GError **error); +gboolean idle_connection_set_status (IdleConnection *obj, GHashTable *statuses, GError **error); +gboolean idle_connection_request_rename (IdleConnection *obj, const char *name, GError **error); + +gboolean idle_connection_hton(IdleConnection *obj, const gchar *input, gchar **output, GError **error); +gboolean idle_connection_ntoh(IdleConnection *obj, const gchar *input, gchar **output, GError **error); + +G_END_DECLS + +#endif /* #ifndef __IDLE_CONNECTION_H__*/ diff --git a/src/idle-dns-resolver.c b/src/idle-dns-resolver.c new file mode 100644 index 0000000..76f46a1 --- /dev/null +++ b/src/idle-dns-resolver.c @@ -0,0 +1,566 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 "idle-dns-resolver.h" + +#define idle_dns_result_new() \ + (g_slice_new(IdleDNSResult)) +#define idle_dns_result_new0() \ + (g_slice_new0(IdleDNSResult)) + +void idle_dns_result_destroy(IdleDNSResult *result) +{ + if (result->ai_next) + { + idle_dns_result_destroy(result->ai_next); + } + + g_slice_free(IdleDNSResult, result); +} + +/* don't use crappy sofia */ +#if 0 +#include "gintset.h" + +#define SU_ROOT_MAGIC_T IdleDNSResolver +#include <sofia-sip/su_source.h> +#include <sofia-sip/su_wait.h> +#define HAVE_SU_WAIT_H 1 +#include <sofia-sip/sresolv.h> + +#include <glib.h> + +#include <string.h> + +struct _IdleDNSResolver +{ + sres_resolver_t *resolver; + su_root_t *root; + + GHashTable *queries; + + guint source_id; + + guint serial; +}; + +typedef struct _IdleDNSQueryData IdleDNSQueryData; + +struct _IdleDNSQueryData +{ + IdleDNSResultCallback callback; + guint port; + guint query_id; + guint refcount; + GIntSet *pending_families; + IdleDNSResult *results; + gpointer user_data; +}; + +typedef struct _IdleDNSQueryInstance IdleDNSQueryInstance; + +struct _IdleDNSQueryInstance +{ + IdleDNSQueryData *data; + uint16_t type; +}; + +#define idle_dns_query_data_new() \ + (g_slice_new(IdleDNSQueryData)) + +#define idle_dns_query_data_new0() \ + (g_slice_new0(IdleDNSQueryData)) + +static IdleDNSQueryData *idle_dns_query_data_ref(IdleDNSQueryData *data) +{ + data->refcount++; + return data; +} + +static void idle_dns_query_data_unref(IdleDNSQueryData *data) +{ + if (!--data->refcount) + { + g_intset_destroy(data->pending_families); + g_slice_free(IdleDNSQueryData, data); + } +} + +#define idle_dns_query_instance_new() \ + (g_slice_new(IdleDNSQueryInstance)) + +#define idle_dns_query_instance_new0() \ + (g_slice_new0(IdleDNSQueryInstance)) + +static void idle_dns_query_instance_destroy(IdleDNSQueryInstance *instance) +{ + if (instance->data) + { + idle_dns_query_data_unref(instance->data); + instance->data = NULL; + } +} + +IdleDNSResolver *idle_dns_resolver_new() +{ + IdleDNSResolver *ret = g_slice_new(IdleDNSResolver); + GSource *source; + + ret->root = su_root_source_create(ret); + g_assert(ret->root); + su_root_threading(ret->root, FALSE); + + source = su_root_gsource(ret->root); + g_assert(source != NULL); + ret->source_id = g_source_attach(source, NULL); + + ret->resolver = sres_resolver_create(ret->root, NULL, TAG_END()); + ret->queries = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)(idle_dns_query_instance_destroy)); + + return ret; +} + +void idle_dns_resolver_destroy(IdleDNSResolver *res) +{ + if (res->resolver) + { + sres_resolver_unref(res->resolver); + res->resolver = NULL; + } + + if (res->root) + { + su_root_destroy(res->root); + res->root = NULL; + } + + if (res->source_id) + { + g_source_remove(res->source_id); + res->source_id = 0; + } + + if (res->queries) + { + g_hash_table_destroy(res->queries); + res->queries = NULL; + } + + g_slice_free(IdleDNSResolver, res); +} + +struct _call_callback_helper +{ + guint id; + IdleDNSResult *results; + IdleDNSResultCallback callback; + gpointer user_data; +}; + +static gboolean call_callback_idle(gpointer user_data) +{ + struct _call_callback_helper *helper = user_data; + + helper->callback(helper->id, helper->results, helper->user_data); + + g_free(user_data); + return FALSE; +} + +static void sres_answer_callback(sres_context_t *ctx, sres_query_t *query, sres_record_t **answers) +{ + IdleDNSResolver *resolver = (IdleDNSResolver *)(ctx); + IdleDNSQueryInstance *instance; + IdleDNSQueryData *data; + IdleDNSResult *results; + IdleDNSResult *tail; + int i; + IdleDNSResultCallback callback; + gpointer user_data; + struct _call_callback_helper *helper = g_new0(struct _call_callback_helper, 1); + guint id; + guint port; + + instance = g_hash_table_lookup(resolver->queries, query); + + if (!instance) + { + g_debug("%s: invalid or cancelled context %p, ignoring", G_STRFUNC, ctx); + return; + } + + data = instance->data; + + g_intset_remove(data->pending_families, instance->type); + + callback = data->callback; + user_data = data->user_data; + port = data->port; + id = data->query_id; + results = data->results; + + for (tail = results; tail && tail->ai_next; tail = tail->ai_next); + + sres_sort_answers(resolver->resolver, answers); + + for (i = 0; answers && answers[i] != NULL; i++) + { + IdleDNSResult *result; + int ai_family; + int ai_socktype = SOCK_STREAM; + int ai_protocol = 0; + struct sockaddr *ai_addr; + socklen_t ai_addrlen; + + switch (answers[i]->sr_record->r_type) + { + case sres_type_a: + { + struct sockaddr_in *sin; + + sin = g_new(struct sockaddr_in, 1); + + sin->sin_family = ai_family = AF_INET; + sin->sin_port = port; + sin->sin_addr = answers[i]->sr_a->a_addr; + + ai_addrlen = sizeof(struct sockaddr_in); + + ai_addr = (struct sockaddr *)(sin); + }; + break; + case sres_type_aaaa: + { + struct sockaddr_in6 *sin6; + + sin6 = g_new0(struct sockaddr_in6, 1); + + sin6->sin6_family = ai_family = AF_INET6; + sin6->sin6_port = port; + memcpy(sin6->sin6_addr.s6_addr, answers[i]->sr_aaaa->aaaa_addr.u6_addr, 16); + + /* FIXME find out about those flow and scope fields, they are currently just set to zero */ + + ai_addrlen = sizeof(struct sockaddr_in6); + + ai_addr = (struct sockaddr *)(sin6); + }; + break; + case sres_type_a6: + { + struct sockaddr_in6 *sin6; + + sin6 = g_new0(struct sockaddr_in6, 1); + + sin6->sin6_family = ai_family = AF_INET6; + sin6->sin6_port = port; + memcpy(sin6->sin6_addr.s6_addr, answers[i]->sr_a6->a6_suffix.u6_addr, 16); + + ai_addrlen = sizeof(struct sockaddr_in6); + + ai_addr = (struct sockaddr *)(sin6); + } + break; + default: + { + g_debug("%s: unsupported address family %u encountered, ignoring", G_STRFUNC, answers[i]->sr_record->r_type); + continue; + } + break; + } + + result = idle_dns_result_new0(); + + result->ai_family = ai_family; + result->ai_socktype = ai_socktype; + result->ai_protocol = ai_protocol; + + result->ai_addr = ai_addr; + result->ai_addrlen = ai_addrlen; + + if (tail) + { + tail->ai_next = result; + } + + if (!results) + { + results = result; + } + + tail = result; + } + + sres_free_answers(resolver->resolver, answers); + + data->results = results; + + /* + * FIXME this sucks. We have to return from this function before we can destroy the resolver (which calling of callback can lead to) so we need to trampoline it with g_idle_add + */ + + helper->callback = callback; + helper->id = id; + helper->results = results; + helper->user_data = user_data; + + if (!g_intset_size(data->pending_families)) + { + g_idle_add(call_callback_idle, helper); + } + + g_hash_table_remove(resolver->queries, query); +} + +guint idle_dns_resolver_query(IdleDNSResolver *resolver, const gchar *name, guint port, IdleDNSResultCallback callback, gpointer user_data) +{ + IdleDNSQueryData *data; + guint query_id = resolver->serial++; + sres_query_t *sofia_query; + static const uint16_t types[] = {sres_type_a, sres_type_aaaa, sres_type_a6}; + int i; + + g_debug("%s: resolving %s:%u", G_STRFUNC, name, port); + + data = idle_dns_query_data_new(); + + data->callback = callback; + data->user_data = user_data; + data->query_id = query_id; + data->port = htons((unsigned short)(port)); + data->results = NULL; + data->refcount = 0; + data->pending_families = g_intset_new(); + + for (i = 0; i < (sizeof(types) / sizeof(uint16_t)); i++) + { + IdleDNSQueryInstance *instance = idle_dns_query_instance_new(); + instance->data = idle_dns_query_data_ref(data); + instance->type = types[i]; + + sofia_query = sres_query(resolver->resolver, + sres_answer_callback, + (sres_context_t *)(resolver), + types[i], + name); + + g_intset_add(data->pending_families, types[i]); + + g_hash_table_insert(resolver->queries, sofia_query, instance); + } + + return query_id; +} + +static gboolean queries_remove_foreach_func(gpointer key, gpointer value, gpointer user_data) +{ + IdleDNSQueryData *data = (IdleDNSQueryData *)(value); + + return (data->query_id = GPOINTER_TO_UINT(user_data)); +} + +void idle_dns_resolver_cancel_query(IdleDNSResolver *resolver, guint query_id) +{ + g_hash_table_foreach_remove(resolver->queries, queries_remove_foreach_func, GUINT_TO_POINTER(query_id)); +} +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +typedef struct _IdleDNSQueryData IdleDNSQueryData; + +struct _IdleDNSQueryData +{ + gchar *name; + guint port; + IdleDNSResultCallback cb; + gpointer user_data; + guint source_id; +}; + +#define idle_dns_query_data_new() \ + (g_slice_new(IdleDNSQueryData)) +#define idle_dns_query_data_new0() \ + (g_slice_new0(IdleDNSQueryData)) + +static void idle_dns_query_data_destroy(IdleDNSQueryData *data) +{ + g_free(data->name); + g_slice_free(IdleDNSQueryData, data); +} + +struct _IdleDNSResolver +{ + GHashTable *queries; + guint serial; +}; + +IdleDNSResolver * +idle_dns_resolver_new() +{ + IdleDNSResolver *ret = g_slice_new(IdleDNSResolver); + + ret->queries = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)(idle_dns_query_data_destroy)); + ret->serial = 0; + + return ret; +} + +void +idle_dns_resolver_destroy(IdleDNSResolver *res) +{ + g_hash_table_destroy(res->queries); + g_slice_free(IdleDNSResolver, res); +} + +struct _idle_helper +{ + IdleDNSResolver *res; + guint serial; +}; + +#define _idle_helper_new() \ + (g_slice_new(struct _idle_helper)) +#define _idle_helper_new0() \ + (g_slice_new0(struct _idle_helper)) + +static void +_idle_helper_destroy(struct _idle_helper *helper) +{ + g_slice_free(struct _idle_helper, helper); +} + +static gboolean +_resolve_idle_func(struct _idle_helper *helper) +{ + IdleDNSQueryData *data = g_hash_table_lookup(helper->res->queries, + GUINT_TO_POINTER(helper->serial)); + struct addrinfo *info = NULL; + struct addrinfo *cur; + int rc; + IdleDNSResult *results = NULL, *tail = NULL; + IdleDNSResultCallback cb; + gpointer user_data; + gchar *service = g_strdup_printf("%u", data->port); + + cb = data->cb; + user_data = data->user_data; + + rc = getaddrinfo(data->name, service, NULL, &info); + + if (rc) + { + g_debug("%s: getaddrinfo(): %s", G_STRFUNC, gai_strerror(rc)); + return FALSE; + } + + for (cur = info; cur != NULL; cur = cur->ai_next) + { + IdleDNSResult *result = idle_dns_result_new(); + g_debug("%s: got result with family %u", G_STRFUNC, cur->ai_family); + + result->ai_family = cur->ai_family; + result->ai_socktype = cur->ai_socktype; + result->ai_protocol = cur->ai_protocol; + result->ai_addr = cur->ai_addr; + result->ai_addrlen = cur->ai_addrlen; + result->ai_next = NULL; + + if (tail) + { + tail->ai_next = result; + } + + if (!results) + { + results = result; + } + + tail = result; + } + + g_hash_table_remove(helper->res->queries, GUINT_TO_POINTER(helper->serial)); + cb(helper->serial, results, user_data); + freeaddrinfo(info); + g_free(service); + + return FALSE; +} + +guint +idle_dns_resolver_query(IdleDNSResolver *res, const gchar *name, guint port, IdleDNSResultCallback cb, gpointer user_data) +{ + IdleDNSQueryData *data; + struct _idle_helper *helper; + guint ret; + + g_assert (res != NULL); + g_assert (name != NULL); + g_assert (port != 0); + g_assert (cb != NULL); + + ret = res->serial++; + + helper = _idle_helper_new(); + helper->res = res; + helper->serial = ret; + + data = idle_dns_query_data_new(); + + data->name = g_strdup(name); + data->port = port; + data->cb = cb; + data->user_data = user_data; + data->source_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc)(_resolve_idle_func), + helper, + (GDestroyNotify)(_idle_helper_destroy)); + + g_hash_table_insert(res->queries, GUINT_TO_POINTER(ret), data); + + return ret; +} + +void +idle_dns_resolver_cancel_query(IdleDNSResolver *res, guint id) +{ + IdleDNSQueryData *data; + + g_assert(res); + + data = g_hash_table_lookup(res->queries, GUINT_TO_POINTER(id)); + + if (!data) + { + g_debug("%s query %u not found!", G_STRFUNC, id); + return; + } + + g_source_remove(data->source_id); + g_hash_table_remove(res->queries, GUINT_TO_POINTER(id)); +} + diff --git a/src/idle-dns-resolver.h b/src/idle-dns-resolver.h new file mode 100644 index 0000000..f456181 --- /dev/null +++ b/src/idle-dns-resolver.h @@ -0,0 +1,65 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <sys/types.h> +#include <netdb.h> + +G_BEGIN_DECLS + +typedef struct _IdleDNSResolver IdleDNSResolver; +typedef struct _IdleDNSResult IdleDNSResult; + +struct _IdleDNSResult +{ + /* as passed to socket() */ + int ai_family; + int ai_socktype; + int ai_protocol; + + /* as passed to connect() */ + struct sockaddr *ai_addr; + socklen_t ai_addrlen; + + /* pointer to the next list member or NULL if this is the last one */ + struct _IdleDNSResult *ai_next; +}; + +void idle_dns_result_destroy(IdleDNSResult *result); + +/* + * id: the query identifier as returned by query() + * results: a linked list of _IdleDNSResult structs which should be freed with idle_dns_result_destroy + * user_data: the user_data pointer as passed to query() + */ + +typedef void (*IdleDNSResultCallback)(guint id, IdleDNSResult *results, gpointer user_data); + +IdleDNSResolver *idle_dns_resolver_new(); +void idle_dns_resolver_destroy(IdleDNSResolver *); + +/* + * returns: the ID of the query, which can be passed to cancel_query() + */ + +guint idle_dns_resolver_query(IdleDNSResolver *, const gchar *name, guint port, IdleDNSResultCallback callback, gpointer user_data); +void idle_dns_resolver_cancel_query(IdleDNSResolver *, guint id); + +G_END_DECLS diff --git a/src/idle-handle-set.c b/src/idle-handle-set.c new file mode 100644 index 0000000..0ef8606 --- /dev/null +++ b/src/idle-handle-set.c @@ -0,0 +1,195 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 "gintset.h" + +#include "idle-handles.h" +#include "idle-handles-private.h" + +#include "idle-handle-set.h" + +struct _IdleHandleSet +{ + IdleHandleStorage *storage; + GIntSet *intset; + TpHandleType type; +}; + +IdleHandleSet *idle_handle_set_new(IdleHandleStorage *storage, TpHandleType type) +{ + IdleHandleSet *set; + + set = g_new(IdleHandleSet, 1); + set->storage = storage; + set->intset = g_intset_new(); + set->type = type; + + return set; +} + +static void freer(IdleHandleSet *set, IdleHandle handle, gpointer userdata) +{ + idle_handle_set_remove(set, handle); +} + +void idle_handle_set_destroy(IdleHandleSet *set) +{ + idle_handle_set_foreach(set, freer, NULL); + g_intset_destroy(set->intset); + g_free(set); +} + +void idle_handle_set_add(IdleHandleSet *set, IdleHandle handle) +{ + g_return_if_fail(set != NULL); + g_return_if_fail(handle != 0); + + if (!g_intset_is_member(set->intset, handle)) + { + if (!idle_handle_ref(set->storage, set->type, handle)) + { + return; + } + + g_intset_add(set->intset, handle); + } +} + +gboolean idle_handle_set_remove(IdleHandleSet *set, IdleHandle handle) +{ + if ((set == NULL) || (handle == 0)) + { + return FALSE; + } + + if (g_intset_is_member(set->intset, handle)) + { + if (idle_handle_unref(set->storage, set->type, handle)) + { + g_intset_remove(set->intset, handle); + return TRUE; + } + else + { + return FALSE; + } + } + else + { + return FALSE; + } +} + +gboolean idle_handle_set_contains(IdleHandleSet *set, IdleHandle handle) +{ + return g_intset_is_member(set->intset, handle); +} + +typedef struct __idle_handle_set_foreach_data +{ + IdleHandleSet *set; + IdleHandleFunc func; + gpointer userdata; +} _idle_handle_set_foreach_data; + +static void foreach_helper(guint i, gpointer userdata) +{ + _idle_handle_set_foreach_data *data; + + data = (_idle_handle_set_foreach_data *)(userdata); + + data->func(data->set, i, data->userdata); +} + +void idle_handle_set_foreach(IdleHandleSet *set, IdleHandleFunc func, gpointer userdata) +{ + _idle_handle_set_foreach_data data = {set, func, userdata}; + g_intset_foreach(set->intset, foreach_helper, &data); +} + +gint idle_handle_set_size(IdleHandleSet *set) +{ + return (set != NULL) ? g_intset_size(set->intset) : 0; +} + +GArray *idle_handle_set_to_array(IdleHandleSet *set) +{ + if (set != NULL) + { + return g_intset_to_array(set->intset); + } + else + { + return NULL; + } +} + +static void _idle_handle_set_ref_one(guint handle, gpointer data) +{ + IdleHandleSet *set; + + set = (IdleHandleSet *)(data); + idle_handle_ref(set->storage, set->type, handle); +} + +GIntSet *idle_handle_set_update(IdleHandleSet *set, const GIntSet *add) +{ + GIntSet *ret, *tmp; + + if ((set == NULL) || (add == NULL)) + { + return NULL; + } + + ret = g_intset_difference(add, set->intset); + g_intset_foreach(ret, _idle_handle_set_ref_one, set); + + tmp = g_intset_union(add, set->intset); + g_intset_destroy(set->intset); + set->intset = tmp; + + return ret; +} + +static void _idle_handle_set_unref_one(guint handle, gpointer data) +{ + IdleHandleSet *set = (IdleHandleSet *)(data); + idle_handle_unref(set->storage, set->type, handle); +} + +GIntSet *idle_handle_set_difference_update(IdleHandleSet *set, const GIntSet *remove) +{ + GIntSet *ret, *tmp; + + if ((set == NULL) || (remove == NULL)) + { + return NULL; + } + + ret = g_intset_intersection(remove, set->intset); + g_intset_foreach(ret, _idle_handle_set_unref_one, set); + + tmp = g_intset_difference(set->intset, remove); + g_intset_destroy(set->intset); + set->intset = tmp; + + return ret; +} diff --git a/src/idle-handle-set.h b/src/idle-handle-set.h new file mode 100644 index 0000000..fdb0d7c --- /dev/null +++ b/src/idle-handle-set.h @@ -0,0 +1,50 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_HANDLE_SET_H__ +#define __IDLE_HANDLE_SET_H__ + +#include <glib.h> + +#include "gintset.h" +#include "idle-handles.h" +#include "telepathy-constants.h" + +G_BEGIN_DECLS + +typedef struct _IdleHandleSet IdleHandleSet; +typedef void (*IdleHandleFunc)(IdleHandleSet *set, IdleHandle handle, gpointer userdata); + +IdleHandleSet *idle_handle_set_new(IdleHandleStorage *storage, TpHandleType type); +void idle_handle_set_destroy(IdleHandleSet *set); + +void idle_handle_set_add(IdleHandleSet *set, IdleHandle handle); +gboolean idle_handle_set_remove(IdleHandleSet *set, IdleHandle handle); +gboolean idle_handle_set_contains(IdleHandleSet *set, IdleHandle handle); + +void idle_handle_set_foreach(IdleHandleSet *set, IdleHandleFunc func, gpointer userdata); + +gint idle_handle_set_size(IdleHandleSet *set); +GArray *idle_handle_set_to_array(IdleHandleSet *set); + +GIntSet *idle_handle_set_update(IdleHandleSet *set, const GIntSet *add); +GIntSet *idle_handle_set_difference_update(IdleHandleSet *set, const GIntSet *remove); + +#endif /* __IDLE_HANDLE_SET_H__ */ diff --git a/src/idle-handles-private.h b/src/idle-handles-private.h new file mode 100644 index 0000000..d094ae4 --- /dev/null +++ b/src/idle-handles-private.h @@ -0,0 +1,53 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_HANDLES_PRIVATE_H__ +#define __IDLE_HANDLES_PRIVATE_H__ + +#include <glib.h> + +#include "idle-connection.h" +#include "gheap.h" + +typedef struct _IdleHandlePriv IdleHandlePriv; + +struct _IdleHandlePriv +{ + guint refcount; + gchar *string; + IdleContactPresence *cp; +}; + +struct _IdleHandleStorage +{ + GHashTable *contact_handles; + GHashTable *room_handles; + + GHashTable *contact_strings; + GHashTable *room_strings; + + guint contact_serial; + guint room_serial; + + GHeap *contact_unused; + GHeap *room_unused; +}; + +#endif diff --git a/src/idle-handles.c b/src/idle-handles.c new file mode 100644 index 0000000..b7efb8c --- /dev/null +++ b/src/idle-handles.c @@ -0,0 +1,589 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <string.h> +#include <ctype.h> + +#include "idle-handles.h" +#include "idle-handles-private.h" + +#include "idle-connection.h" + +#include "gintset.h" + +#define idle_handle_priv_new() (g_slice_new0(IdleHandlePriv)) + +gboolean idle_nickname_is_valid(const gchar *nickname) +{ + gsize len; + gunichar ucs4char; + const gchar *char_pos = nickname; + + len = g_utf8_strlen(nickname, -1); + + if (!len) + { + return FALSE; + } + + while (char_pos != NULL) + { + ucs4char = g_utf8_get_char_validated(char_pos, -1); + + switch (*char_pos) + { + case '[': + case ']': + case '\\': + case '`': + case '_': + case '^': + case '{': + case '|': + case '}': + case '-': + break; + case '\0': + { + return TRUE; + } + break; + default: + { + if (!(g_unichar_isalpha(ucs4char) || ((char_pos != nickname) && g_unichar_isdigit(ucs4char)))) + { + return FALSE; + } + } + break; + } + + char_pos = g_utf8_find_next_char(char_pos, NULL); + } + + return TRUE; +} + +gboolean idle_channelname_is_valid(const gchar *channel) +{ + const static gchar not_allowed_chars[6] = {' ', '\007', ',', '\r', '\n', ':'}; + const gchar *tmp, *tmp2; + int i; + gsize len; + + if ((channel[0] != '#') && (channel[0] != '!') && (channel[0] != '&') && (channel[0] != '+')) + { + return FALSE; + } + + len = strlen(channel); + + if ((len < 2) || (len > 50)) + { + return FALSE; + } + + if (channel[0] == '!') + { + for (i=0; i<5; i++) + { + if (!g_ascii_isupper(channel[i+1]) && !isdigit(channel[i+1])) + { + return FALSE; + } + } + } + + tmp = strchr(channel+1, ':'); + + if (tmp != NULL) + { + for (tmp2 = channel+1; tmp2 != tmp; tmp2++) + { + for (i=0; i<6; i++) + { + if (*tmp2 == not_allowed_chars[i]) + { + return FALSE; + } + } + } + + for (tmp2 = tmp+1; tmp2 != channel+len; tmp2++) + { + for (i=0; i<6; i++) + { + if (*tmp2 == not_allowed_chars[i]) + { + return FALSE; + } + } + } + } + else + { + for (tmp2 = channel+1; tmp2 != channel+len; tmp2++) + { + for (i=0; i<6; i++) + { + if (*tmp2 == not_allowed_chars[i]) + { + return FALSE; + } + } + } + + } + + return TRUE; +} + +static void handle_priv_free(IdleHandlePriv *priv) +{ + g_assert(priv != NULL); + g_free(priv->string); + + if (priv->cp != NULL) + { + idle_contact_presence_free(priv->cp); + } + + g_slice_free(IdleHandlePriv, priv); +} + +static IdleHandle idle_handle_alloc(IdleHandleStorage *storage, TpHandleType type) +{ + IdleHandle ret; + + g_assert(storage != NULL); + + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + { + ret = GPOINTER_TO_INT(g_heap_extract_first(storage->contact_unused)); + + if (ret == 0) + { + ret = storage->contact_serial++; + } + } + break; + case TP_HANDLE_TYPE_ROOM: + { + ret = GPOINTER_TO_INT(g_heap_extract_first(storage->room_unused)); + + if (ret == 0) + { + ret = storage->room_serial++; + } + } + break; + case TP_HANDLE_TYPE_LIST: + default: + { + g_debug("%s: unsupported handle type %u", G_STRFUNC, type); + ret = 0; + } + break; + } + +/* g_debug("%s: returning %u (type %u)", G_STRFUNC, ret, type);*/ + + return ret; +} + +IdleHandlePriv *handle_priv_lookup(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle) +{ + IdleHandlePriv *priv; + + g_assert(storage != NULL); + + if (handle == 0) + { + return NULL; + } + + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + { + priv = g_hash_table_lookup(storage->contact_handles, GINT_TO_POINTER(handle)); + } + break; + case TP_HANDLE_TYPE_ROOM: + { + priv = g_hash_table_lookup(storage->room_handles, GINT_TO_POINTER(handle)); + } + break; + case TP_HANDLE_TYPE_LIST: + default: + { + g_critical("%s: Only TP_HANDLE_TYPE_CONTACT and TP_HANDLE_TYPE_ROOM supported!", G_STRFUNC); + return NULL; + } + break; + } + + return priv; +} + +void handle_priv_remove(IdleHandleStorage *storage, TpHandleType type, IdleHandlePriv *priv, IdleHandle handle) +{ + g_assert(storage != NULL); + + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + { + g_hash_table_remove(storage->contact_strings, priv->string); + g_hash_table_remove(storage->contact_handles, GINT_TO_POINTER(handle)); + + if (handle == storage->contact_serial-1) + { + /* take advantage of good luck ;) */ + storage->contact_serial--; + } + else + { + g_heap_add(storage->contact_unused, GINT_TO_POINTER(handle)); + } + } + break; + case TP_HANDLE_TYPE_ROOM: + { + g_hash_table_remove(storage->room_strings, priv->string); + g_hash_table_remove(storage->room_handles, GINT_TO_POINTER(handle)); + + if (handle == storage->room_serial-1) + { + storage->room_serial--; + } + else + { + g_heap_add(storage->room_unused, GINT_TO_POINTER(handle)); + } + } + break; + case TP_HANDLE_TYPE_LIST: + default: + { + g_critical("%s: Only TP_HANDLE_TYPE_CONTACT and TP_HANDLE_TYPE_ROOM supported!", G_STRFUNC); + return; + } + break; + } +} + +gboolean idle_handle_type_is_valid(TpHandleType type) +{ + switch (type) + { + case TP_HANDLE_TYPE_CONTACT: + case TP_HANDLE_TYPE_ROOM: + { + return TRUE; + } + break; + case TP_HANDLE_TYPE_LIST: + default: + { + return FALSE; + } + break; + } +} + +static guint g_strncase_hash(gconstpointer key) +{ + guint ret; + gchar *tmp; + + tmp = g_utf8_strdown(key, 32); + ret = g_str_hash(tmp); + + g_free(tmp); + + return ret; +} + +static gboolean g_strncase_equal(gconstpointer a, gconstpointer b) +{ + gchar *s1, *s2; + gboolean ret; + + s1 = g_utf8_casefold(a, -1); + s2 = g_utf8_casefold(b, -1); + + ret = (strcmp(s1, s2) == 0); + + g_free(s1); + g_free(s2); + + return ret; +} + +static gint idle_handle_compare(gconstpointer a, gconstpointer b) +{ + return (a < b) ? -1 : (a == b) ? 0 : 1; +} + +IdleHandleStorage *idle_handle_storage_new() +{ + IdleHandleStorage *ret; + + ret = g_slice_new0(IdleHandleStorage); + + ret->contact_handles = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)(handle_priv_free)); + ret->room_handles = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)(handle_priv_free)); + + ret->contact_strings = g_hash_table_new_full(g_strncase_hash, g_strncase_equal, NULL, NULL); + ret->room_strings = g_hash_table_new_full(g_strncase_hash, g_strncase_equal, NULL, NULL); + + ret->contact_unused = g_heap_new(idle_handle_compare); + ret->room_unused = g_heap_new(idle_handle_compare); + + ret->contact_serial = 1; + ret->room_serial = 1; + + return ret; +} + +void idle_handle_storage_destroy(IdleHandleStorage *storage) +{ + g_assert(storage != NULL); + g_assert(storage->contact_handles != NULL); + g_assert(storage->room_handles != NULL); + + g_hash_table_destroy(storage->contact_handles); + g_hash_table_destroy(storage->room_handles); + + g_hash_table_destroy(storage->contact_strings); + g_hash_table_destroy(storage->room_strings); + + g_heap_destroy(storage->contact_unused); + g_heap_destroy(storage->room_unused); + + g_slice_free(IdleHandleStorage, storage); +} + +gboolean idle_handle_is_valid(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle) +{ + return (handle_priv_lookup(storage, type, handle) != NULL); +} + +gboolean idle_handle_ref(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle) +{ + IdleHandlePriv *priv; + + priv = handle_priv_lookup(storage, type, handle); + + if (priv == NULL) + { + return FALSE; + } + else + { + priv->refcount++; + return TRUE; + } +} + +gboolean idle_handle_unref(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle) +{ + IdleHandlePriv *priv; + + priv = handle_priv_lookup(storage, type, handle); + + if (priv == NULL) + { + return FALSE; + } + else + { + g_assert(priv->refcount > 0); + + priv->refcount--; + + if (priv->refcount == 0) + { + handle_priv_remove(storage, type, priv, handle); + } + + return TRUE; + } +} + +const char *idle_handle_inspect(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle) +{ + IdleHandlePriv *priv; + + priv = handle_priv_lookup(storage, type, handle); + + if (priv == NULL) + { + return NULL; + } + else + { + return priv->string; + } +} + +IdleHandle idle_handle_for_contact(IdleHandleStorage *storage, const char *nickname) +{ + IdleHandle handle; + + g_assert(storage != NULL); + + if ((!nickname) || (nickname[0] == '\0')) + { + g_debug("%s: handle for invalid nickname requested", G_STRFUNC); + return 0; + } + + if (!idle_nickname_is_valid(nickname)) + { + g_debug("%s: nickname (%s) isn't valid!", G_STRFUNC, nickname); + return 0; + } + + handle = GPOINTER_TO_INT(g_hash_table_lookup(storage->contact_strings, nickname)); + + if (handle == 0) + { + handle = idle_handle_alloc(storage, TP_HANDLE_TYPE_CONTACT); + } + + if (handle_priv_lookup(storage, TP_HANDLE_TYPE_CONTACT, handle) == NULL) + { + IdleHandlePriv *priv; + priv = idle_handle_priv_new(); + priv->string = g_strdup(nickname); + + g_hash_table_insert(storage->contact_handles, GINT_TO_POINTER(handle), priv); + g_hash_table_insert(storage->contact_strings, priv->string, GINT_TO_POINTER(handle)); + } + + return handle; +} + +gboolean idle_handle_for_room_exists(IdleHandleStorage *storage, const char *channel_up) +{ + IdleHandle handle; + gchar *channel; + + g_assert(storage != NULL); + + channel = g_ascii_strdown(channel_up, -1); + + if ((channel == NULL) || (channel[0] == '\0')) + { + return FALSE; + } + else + { + handle = GPOINTER_TO_INT(g_hash_table_lookup(storage->room_strings, channel)); + } + + g_free(channel); + + return handle_priv_lookup(storage, TP_HANDLE_TYPE_ROOM, handle) != NULL; +} + +IdleHandle idle_handle_for_room(IdleHandleStorage *storage, const char *channel) +{ + IdleHandle handle; + + g_assert(storage != NULL); + + if ((channel == NULL) || (channel[0] == '\0')) + { + g_debug("%s: handle for a invalid channel requested", G_STRFUNC); + return 0; + } + + if (!idle_channelname_is_valid(channel)) + { + g_debug("%s: channel name (%s) not valid!", G_STRFUNC, channel); + return 0; + } + + handle = GPOINTER_TO_INT(g_hash_table_lookup(storage->room_strings, channel)); + + if (handle == 0) + { + handle = idle_handle_alloc(storage, TP_HANDLE_TYPE_ROOM); + } + + if (handle_priv_lookup(storage, TP_HANDLE_TYPE_ROOM, handle) == NULL) + { + IdleHandlePriv *priv; + priv = idle_handle_priv_new(); + priv->string = g_strdup(channel); + + g_hash_table_insert(storage->room_handles, GINT_TO_POINTER(handle), priv); + g_hash_table_insert(storage->room_strings, priv->string, GINT_TO_POINTER(handle)); + } + + return handle; +} + +gboolean idle_handle_set_presence(IdleHandleStorage *storage, IdleHandle handle, IdleContactPresence *cp) +{ + IdleHandlePriv *priv; + + g_assert(storage != NULL); + g_assert(handle != 0); + + priv = handle_priv_lookup(storage, TP_HANDLE_TYPE_CONTACT, handle); + + if (priv == NULL) + { + return FALSE; + } + else + { + priv->cp = cp; + } + + return TRUE; +} + +IdleContactPresence *idle_handle_get_presence(IdleHandleStorage *storage, IdleHandle handle) +{ + IdleHandlePriv *priv; + + g_assert(storage != NULL); + g_assert(handle != 0); + + priv = handle_priv_lookup(storage, TP_HANDLE_TYPE_CONTACT, handle); + + if (priv == NULL) + { + return NULL; + } + else + { + return priv->cp; + } +} + diff --git a/src/idle-handles.h b/src/idle-handles.h new file mode 100644 index 0000000..2eb5365 --- /dev/null +++ b/src/idle-handles.h @@ -0,0 +1,56 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_HANDLES_H__ +#define __IDLE_HANDLES_H__ + +#include <glib.h> + +typedef struct _IdleHandleStorage IdleHandleStorage; +typedef guint IdleHandle; + +#include "idle-connection.h" +#include "telepathy-constants.h" + +G_BEGIN_DECLS + +gboolean idle_handle_type_is_valid(TpHandleType type); + +IdleHandleStorage *idle_handle_storage_new(); +void idle_handle_storage_destroy(IdleHandleStorage *storage); + +gboolean idle_nickname_is_valid(const gchar *nickname); +gboolean idle_channelname_is_valid(const gchar *channelname); + +gboolean idle_handle_is_valid(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle); +gboolean idle_handle_ref(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle); +gboolean idle_handle_unref(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle); +const char *idle_handle_inspect(IdleHandleStorage *storage, TpHandleType type, IdleHandle handle); + +IdleHandle idle_handle_for_contact(IdleHandleStorage *storage, const char *nickname); +gboolean idle_handle_for_room_exists(IdleHandleStorage *storage, const char *channel); +IdleHandle idle_handle_for_room(IdleHandleStorage *storage, const char *channel); + +gboolean idle_handle_set_presence(IdleHandleStorage *storage, IdleHandle contact_handle, IdleContactPresence *presence); +IdleContactPresence *idle_handle_get_presence(IdleHandleStorage *storage, IdleHandle contact_handle); + +G_END_DECLS + +#endif /* __IDLE_HANDLES_H__ */ diff --git a/src/idle-im-channel-signals-marshal.list b/src/idle-im-channel-signals-marshal.list new file mode 100644 index 0000000..d4b1325 --- /dev/null +++ b/src/idle-im-channel-signals-marshal.list @@ -0,0 +1,4 @@ +VOID:VOID +VOID:INT,INT,INT,INT,INT,STRING +VOID:INT,INT,INT,STRING +VOID:INT,INT,STRING diff --git a/src/idle-im-channel.c b/src/idle-im-channel.c new file mode 100644 index 0000000..8982c79 --- /dev/null +++ b/src/idle-im-channel.c @@ -0,0 +1,814 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <dbus/dbus-glib.h> + +#define _GNU_SOURCE +#include <string.h> +#include <time.h> + +#include "idle-connection.h" +#include "idle-handles.h" +#include "telepathy-constants.h" +#include "telepathy-errors.h" +#include "telepathy-helpers.h" +#include "telepathy-interfaces.h" + +#include "idle-im-channel.h" +#include "idle-im-channel-glue.h" +#include "idle-im-channel-signals-marshal.h" + +#define IRC_MSG_MAXLEN 510 + +G_DEFINE_TYPE(IdleIMChannel, idle_im_channel, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + CLOSED, + RECEIVED, + SENT, + SEND_ERROR, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +/* property enum */ +enum +{ + PROP_CONNECTION = 1, + PROP_OBJECT_PATH, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + LAST_PROPERTY_ENUM +}; + +typedef struct _IdleIMPendingMessage IdleIMPendingMessage; + +struct _IdleIMPendingMessage +{ + guint id; + + time_t timestamp; + IdleHandle sender; + + TpChannelTextMessageType type; + + gchar *text; +}; + +/* private structure */ +typedef struct _IdleIMChannelPrivate IdleIMChannelPrivate; + +struct _IdleIMChannelPrivate +{ + IdleConnection *connection; + gchar *object_path; + IdleHandle handle; + + guint recv_id; + GQueue *pending_messages; + + IdleIMPendingMessage *last_msg; + + gboolean closed; + + gboolean dispose_has_run; +}; + +#define _idle_im_pending_new() \ + (g_slice_new(IdleIMPendingMessage)) +#define _idle_im_pending_new0() \ + (g_slice_new0(IdleIMPendingMessage)) + +static void _idle_im_pending_free(IdleIMPendingMessage *msg) +{ + if (msg->text) + { + g_free(msg->text); + } + + g_slice_free(IdleIMPendingMessage, msg); +} + +#define IDLE_IM_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_IM_CHANNEL, IdleIMChannelPrivate)) + +static void +idle_im_channel_init (IdleIMChannel *obj) +{ + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (obj); + + priv->pending_messages = g_queue_new(); + + priv->last_msg = _idle_im_pending_new0(); +} + +static void idle_im_channel_dispose (GObject *object); +static void idle_im_channel_finalize (GObject *object); + +static GObject *idle_im_channel_constructor(GType type, guint n_props, GObjectConstructParam *props) +{ + GObject *obj; + IdleIMChannelPrivate *priv; + DBusGConnection *bus; + IdleHandleStorage *handles; + gboolean valid; + + obj = G_OBJECT_CLASS(idle_im_channel_parent_class)->constructor(type, n_props, props); + priv = IDLE_IM_CHANNEL_GET_PRIVATE(IDLE_IM_CHANNEL(obj)); + + handles = _idle_connection_get_handles(priv->connection); + valid = idle_handle_ref(handles, TP_HANDLE_TYPE_CONTACT, priv->handle); + g_assert(valid); + + bus = tp_get_bus(); + dbus_g_connection_register_g_object(bus, priv->object_path, obj); + + return obj; +} + +static void idle_im_channel_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + IdleIMChannel *chan; + IdleIMChannelPrivate *priv; + + g_assert(object != NULL); + g_assert(IDLE_IS_IM_CHANNEL(object)); + + chan = IDLE_IM_CHANNEL(object); + priv = IDLE_IM_CHANNEL_GET_PRIVATE(chan); + + switch (property_id) + { + case PROP_CONNECTION: + { + g_value_set_object(value, priv->connection); + } + break; + case PROP_OBJECT_PATH: + { + g_value_set_string(value, priv->object_path); + } + break; + case PROP_CHANNEL_TYPE: + { + g_value_set_string(value, TP_IFACE_CHANNEL_TYPE_TEXT); + } + break; + case PROP_HANDLE_TYPE: + { + g_value_set_uint(value, TP_HANDLE_TYPE_CONTACT); + } + break; + case PROP_HANDLE: + { + g_value_set_uint(value, priv->handle); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } + break; + } +} + +static void idle_im_channel_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) +{ + IdleIMChannel *chan = IDLE_IM_CHANNEL(object); + IdleIMChannelPrivate *priv; + + g_assert(chan != NULL); + g_assert(IDLE_IS_IM_CHANNEL(chan)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(chan); + + switch (property_id) + { + case PROP_CONNECTION: + { + priv->connection = g_value_get_object(value); + } + break; + case PROP_OBJECT_PATH: + { + if (priv->object_path) + { + g_free(priv->object_path); + } + + priv->object_path = g_value_dup_string(value); + } + break; + case PROP_HANDLE: + { + priv->handle = g_value_get_uint(value); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } + break; + } +} + +static void +idle_im_channel_class_init (IdleIMChannelClass *idle_im_channel_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_im_channel_class); + GParamSpec *param_spec; + + g_type_class_add_private (idle_im_channel_class, sizeof (IdleIMChannelPrivate)); + + object_class->constructor = idle_im_channel_constructor; + + object_class->get_property = idle_im_channel_get_property; + object_class->set_property = idle_im_channel_set_property; + + object_class->dispose = idle_im_channel_dispose; + object_class->finalize = idle_im_channel_finalize; + + param_spec = g_param_spec_object ("connection", "IdleConnection object", + "The IdleConnection object that owns this " + "IMChannel object.", + IDLE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_string ("object-path", "D-Bus object path", + "The D-Bus object path used for this " + "object on the bus.", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); + + param_spec = g_param_spec_string ("channel-type", "Telepathy channel type", + "The D-Bus interface representing the " + "type of this channel.", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CHANNEL_TYPE, param_spec); + + param_spec = g_param_spec_uint ("handle-type", "Contact handle type", + "The TpHandleType representing a " + "contact handle.", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HANDLE_TYPE, param_spec); + + param_spec = g_param_spec_uint ("handle", "Contact handle", + "The IdleHandle representing the contact " + "with whom this channel communicates.", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HANDLE, param_spec); + + signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[RECEIVED] = + g_signal_new ("received", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__INT_INT_INT_INT_INT_STRING, + G_TYPE_NONE, 6, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SENT] = + g_signal_new ("sent", + G_OBJECT_CLASS_TYPE (idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__INT_INT_STRING, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SEND_ERROR] = + g_signal_new("send-error", + G_OBJECT_CLASS_TYPE(idle_im_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_im_channel_marshal_VOID__INT_INT_INT_STRING, + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_im_channel_class), &dbus_glib_idle_im_channel_object_info); +} + +void +idle_im_channel_dispose (GObject *object) +{ + IdleIMChannel *self = IDLE_IM_CHANNEL (object); + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (self); + + g_assert(object != NULL); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + if (!priv->closed) + { + g_signal_emit(self, signals[CLOSED], 0); + priv->closed = TRUE; + } + + if (G_OBJECT_CLASS (idle_im_channel_parent_class)->dispose) + G_OBJECT_CLASS (idle_im_channel_parent_class)->dispose (object); +} + +void +idle_im_channel_finalize (GObject *object) +{ + IdleIMChannel *self = IDLE_IM_CHANNEL (object); + IdleIMChannelPrivate *priv = IDLE_IM_CHANNEL_GET_PRIVATE (self); + IdleHandleStorage *handles; + IdleIMPendingMessage *msg; + + handles = _idle_connection_get_handles(priv->connection); + idle_handle_unref(handles, TP_HANDLE_TYPE_CONTACT, priv->handle); + + if (priv->object_path) + { + g_free(priv->object_path); + } + + while ((msg = g_queue_pop_head(priv->pending_messages)) != NULL) + { + _idle_im_pending_free(msg); + } + + g_queue_free(priv->pending_messages); + + _idle_im_pending_free(priv->last_msg); + + G_OBJECT_CLASS (idle_im_channel_parent_class)->finalize (object); +} + +gboolean _idle_im_channel_receive(IdleIMChannel *chan, TpChannelTextMessageType type, IdleHandle sender, const gchar *text) +{ + IdleIMChannelPrivate *priv; + IdleIMPendingMessage *msg; + + g_assert(chan != NULL); + g_assert(IDLE_IS_IM_CHANNEL(chan)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(chan); + + msg = _idle_im_pending_new(); + + msg->id = priv->recv_id++; + msg->timestamp = time(NULL); + msg->sender = sender; + msg->type = type; + msg->text = g_strdup(text); + + g_queue_push_tail(priv->pending_messages, msg); + + g_signal_emit(chan, signals[RECEIVED], 0, + msg->id, + msg->timestamp, + msg->sender, + msg->type, + 0, + msg->text); + + g_debug("%s: queued message %u", G_STRFUNC, msg->id); + + return FALSE; +} + +void _idle_im_channel_rename(IdleIMChannel *chan, IdleHandle new) +{ + IdleIMChannelPrivate *priv; + IdleHandleStorage *handles; + gboolean valid; + + g_assert(chan != NULL); + g_assert(IDLE_IS_IM_CHANNEL(chan)); + + g_assert(new != 0); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(chan); + handles = _idle_connection_get_handles(priv->connection); + + idle_handle_unref(handles, TP_HANDLE_TYPE_CONTACT, priv->handle); + priv->handle = new; + valid = idle_handle_ref(handles, TP_HANDLE_TYPE_CONTACT, priv->handle); + g_assert(valid); + + g_debug("%s: changed to handle %u", G_STRFUNC, new); +} + +void _idle_im_channel_nosuchnick(IdleIMChannel *chan) +{ + IdleIMChannelPrivate *priv; + IdleIMPendingMessage *last_msg; + + g_assert(chan != NULL); + g_assert(IDLE_IS_IM_CHANNEL(chan)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(chan); + + g_assert(priv->last_msg != NULL); + last_msg = priv->last_msg; + + /* TODO this is so incorrect, we are assuming it was always the most recent message etc... */ + + g_signal_emit(chan, signals[SEND_ERROR], 0, TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE, last_msg->timestamp, last_msg->type, last_msg->text); +} + +static gint idle_pending_message_compare(gconstpointer msg, gconstpointer id) +{ + IdleIMPendingMessage *message = (IdleIMPendingMessage *)(msg); + + return (message->id != GPOINTER_TO_INT(id)); +} + +/** + * idle_im_channel_acknowledge_pending_messages + * + * Implements DBus method AcknowledgePendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_acknowledge_pending_messages (IdleIMChannel *obj, const GArray *ids, GError **error) +{ + IdleIMChannelPrivate *priv; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_IM_CHANNEL(obj)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(obj); + + for (i=0; i < ids->len; i++) + { + GList *node; + IdleIMPendingMessage *msg; + guint id = g_array_index(ids, guint, i); + + node = g_queue_find_custom(priv->pending_messages, + GINT_TO_POINTER(id), + idle_pending_message_compare); + + if (!node) + { + g_debug("%s: message id %u not found", G_STRFUNC, id); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "message id %u not found", id); + + return FALSE; + } + + msg = (IdleIMPendingMessage *)(node->data); + + g_debug("%s: acknowledging message id %u", G_STRFUNC, id); + + g_queue_delete_link(priv->pending_messages, node); + + _idle_im_pending_free(msg); + } + + return TRUE; +} + + +/** + * idle_im_channel_close + * + * Implements DBus method Close + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_close (IdleIMChannel *obj, GError **error) +{ + IdleIMChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_IM_CHANNEL(obj)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(obj); + priv->closed = TRUE; + + g_debug("%s called on %p", G_STRFUNC, obj); + g_signal_emit(obj, signals[CLOSED], 0); + + return TRUE; +} + + +/** + * idle_im_channel_get_channel_type + * + * Implements DBus method GetChannelType + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_channel_type (IdleIMChannel *obj, gchar ** ret, GError **error) +{ + *ret = g_strdup(TP_IFACE_CHANNEL_TYPE_TEXT); + + return TRUE; +} + + +/** + * idle_im_channel_get_handle + * + * Implements DBus method GetHandle + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_handle (IdleIMChannel *obj, guint* ret, guint* ret1, GError **error) +{ + IdleIMChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_IM_CHANNEL(obj)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(obj); + + *ret = TP_HANDLE_TYPE_CONTACT; + *ret1 = priv->handle; + + return TRUE; +} + + +/** + * idle_im_channel_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_get_interfaces (IdleIMChannel *obj, gchar *** ret, GError **error) +{ + const gchar *interfaces[] = {NULL}; + + *ret = g_strdupv((gchar **)(interfaces)); + + return TRUE; +} + + +/** + * idle_im_channel_list_pending_messages + * + * Implements DBus method ListPendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_list_pending_messages (IdleIMChannel *obj, + gboolean clear, + GPtrArray ** ret, + GError **error) +{ + IdleIMChannelPrivate *priv; + guint count; + GPtrArray *messages; + GList *cur; + + g_assert (IDLE_IS_IM_CHANNEL (obj)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE (obj); + + count = g_queue_get_length (priv->pending_messages); + messages = g_ptr_array_sized_new (count); + + for (cur = g_queue_peek_head_link(priv->pending_messages); + cur != NULL; + cur = cur->next) + { + IdleIMPendingMessage *msg = (IdleIMPendingMessage *)(cur->data); + GValueArray *vals; + + vals = g_value_array_new (6); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 0), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 0), msg->id); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 1), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 1), msg->timestamp); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 2), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 2), msg->sender); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 3), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 3), msg->type); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 4), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 4), 0); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 5), G_TYPE_STRING); + g_value_set_string (g_value_array_get_nth (vals, 5), msg->text); + + g_ptr_array_add (messages, vals); + } + + if (clear) + { + IdleIMPendingMessage *msg; + + while ((msg = g_queue_pop_head(priv->pending_messages)) != NULL) + { + _idle_im_pending_free(msg); + } + } + + *ret = messages; + + return TRUE; +} + + +/** + * idle_im_channel_send + * + * Implements DBus method Send + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_im_channel_send (IdleIMChannel *obj, guint type, const gchar * text, GError **error) +{ + IdleIMChannelPrivate *priv; + gchar msg[IRC_MSG_MAXLEN+1]; + const char *recipient; + time_t timestamp; + const gchar *final_text = text; + gsize len; + gchar *part; + gsize headerlen; + + g_assert(obj != NULL); + g_assert(IDLE_IS_IM_CHANNEL(obj)); + + priv = IDLE_IM_CHANNEL_GET_PRIVATE(obj); + + recipient = idle_handle_inspect(_idle_connection_get_handles(priv->connection), TP_HANDLE_TYPE_CONTACT, + priv->handle); + + if ((recipient == NULL) || (strlen(recipient) == 0)) + { + g_debug("%s: invalid recipient", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "invalid recipient"); + + return FALSE; + } + + len = strlen(final_text); + part = (gchar*)final_text; + + if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL) + { + g_snprintf(msg, IRC_MSG_MAXLEN+1, "PRIVMSG %s :", recipient); + } + else if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) + { + g_snprintf(msg, IRC_MSG_MAXLEN+1, "PRIVMSG %s :\001ACTION ", recipient); + } + else if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE) + { + g_snprintf(msg, IRC_MSG_MAXLEN+1, "NOTICE %s :", recipient); + } + else + { + g_debug("%s: invalid message type %u", G_STRFUNC, type); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid message type %u", type); + + return FALSE; + } + + headerlen = strlen(msg); + + while (part < final_text+len) + { + char *br = strchr (part, '\n'); + size_t len = IRC_MSG_MAXLEN-headerlen; + if (br) + { + len = (len < br - part) ? len : br - part; + } + + if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) + { + g_snprintf(msg+headerlen, len + 1, "%s\001", part); + len -= 1; + } + else + { + g_strlcpy(msg+headerlen, part, len + 1); + } + part += len; + if (br) + { + part++; + } + + _idle_connection_send(priv->connection, msg); + } + + timestamp = time(NULL); + + g_signal_emit(obj, signals[SENT], 0, timestamp, type, text); + + if (priv->last_msg->text) + { + g_free(priv->last_msg->text); + } + + priv->last_msg->timestamp = timestamp; + priv->last_msg->type = type; + priv->last_msg->text = g_strdup(text); + + return TRUE; +} + diff --git a/src/idle-im-channel.h b/src/idle-im-channel.h new file mode 100644 index 0000000..ebc308f --- /dev/null +++ b/src/idle-im-channel.h @@ -0,0 +1,69 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_IM_CHANNEL_H__ +#define __IDLE_IM_CHANNEL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleIMChannel IdleIMChannel; +typedef struct _IdleIMChannelClass IdleIMChannelClass; + +struct _IdleIMChannelClass { + GObjectClass parent_class; +}; + +struct _IdleIMChannel { + GObject parent; +}; + +GType idle_im_channel_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_IM_CHANNEL \ + (idle_im_channel_get_type()) +#define IDLE_IM_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_IM_CHANNEL, IdleIMChannel)) +#define IDLE_IM_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_IM_CHANNEL, IdleIMChannelClass)) +#define IDLE_IS_IM_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_IM_CHANNEL)) +#define IDLE_IS_IM_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_IM_CHANNEL)) +#define IDLE_IM_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_IM_CHANNEL, IdleIMChannelClass)) + +gboolean _idle_im_channel_receive(IdleIMChannel *chan, TpChannelTextMessageType type, IdleHandle sender, const gchar *msg); +void _idle_im_channel_rename(IdleIMChannel *chan, IdleHandle new_handle); +void _idle_im_channel_nosuchnick(IdleIMChannel *chan); + +gboolean idle_im_channel_acknowledge_pending_messages (IdleIMChannel *obj, const GArray *ids, GError **error); +gboolean idle_im_channel_close (IdleIMChannel *obj, GError **error); +gboolean idle_im_channel_get_channel_type (IdleIMChannel *obj, gchar ** ret, GError **error); +gboolean idle_im_channel_get_handle (IdleIMChannel *obj, guint* ret, guint* ret1, GError **error); +gboolean idle_im_channel_get_interfaces (IdleIMChannel *obj, gchar *** ret, GError **error); +gboolean idle_im_channel_list_pending_messages (IdleIMChannel *obj, gboolean clear, GPtrArray ** ret, GError **error); +gboolean idle_im_channel_send (IdleIMChannel *obj, guint type, const gchar * text, GError **error); + +G_END_DECLS + +#endif /* #ifndef __IDLE_IM_CHANNEL_H__*/ diff --git a/src/idle-muc-channel-signals-marshal.list b/src/idle-muc-channel-signals-marshal.list new file mode 100644 index 0000000..5863b3c --- /dev/null +++ b/src/idle-muc-channel-signals-marshal.list @@ -0,0 +1,11 @@ +VOID:VOID +VOID:INT,INT +VOID:INT +VOID:VOID +VOID:STRING,BOXED,BOXED,BOXED,BOXED,INT,INT +VOID:INT,INT +VOID:BOXED +VOID:BOXED +VOID:INT,INT,INT,INT,INT,STRING +VOID:INT,INT,INT,STRING +VOID:INT,INT,STRING diff --git a/src/idle-muc-channel.c b/src/idle-muc-channel.c new file mode 100644 index 0000000..4d76053 --- /dev/null +++ b/src/idle-muc-channel.c @@ -0,0 +1,3474 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <dbus/dbus-glib.h> + +#define _GNU_SOURCE +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <time.h> + +#include "idle-muc-channel.h" +#include "idle-muc-channel-signals-marshal.h" + +#include "idle-muc-channel-glue.h" + +#include "idle-connection.h" +#include "idle-handles.h" +#include "idle-handle-set.h" + +#include "telepathy-helpers.h" +#include "telepathy-errors.h" +#include "telepathy-constants.h" +#include "telepathy-interfaces.h" + +#define IRC_MSG_MAXLEN 510 + +#define DEBUGSPIKE {g_debug("at %s, line %u", G_STRFUNC, __LINE__);} + +G_DEFINE_TYPE(IdleMUCChannel, idle_muc_channel, G_TYPE_OBJECT) + +/* signal enum */ +enum +{ + CLOSED, + GROUP_FLAGS_CHANGED, + LOST_MESSAGE, + MEMBERS_CHANGED, + PASSWORD_FLAGS_CHANGED, + PROPERTIES_CHANGED, + PROPERTY_FLAGS_CHANGED, + RECEIVED, + SEND_ERROR, + SENT, + JOIN_READY, + LAST_SIGNAL +}; + +/* property enum */ +enum +{ + PROP_CONNECTION = 1, + PROP_OBJECT_PATH, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + LAST_PROPERTY_ENUM +}; + +typedef enum +{ + MUC_STATE_CREATED = 0, + MUC_STATE_JOINING, + MUC_STATE_NEED_PASSWORD, + MUC_STATE_JOINED, + MUC_STATE_PARTED +} IdleMUCState; + +#define LAST_MODE_FLAG_SHIFT (15) + +typedef enum +{ + MODE_FLAG_CREATOR = 1, + MODE_FLAG_OPERATOR_PRIVILEGE = 2, + MODE_FLAG_VOICE_PRIVILEGE = 4, + + MODE_FLAG_ANONYMOUS = 8, + MODE_FLAG_INVITE_ONLY = 16, + MODE_FLAG_MODERATED= 32, + MODE_FLAG_NO_OUTSIDE_MESSAGES = 64, + MODE_FLAG_QUIET= 128, + MODE_FLAG_PRIVATE= 256, + MODE_FLAG_SECRET= 512, + MODE_FLAG_SERVER_REOP= 1024, + MODE_FLAG_TOPIC_ONLY_SETTABLE_BY_OPS = 2048, + + MODE_FLAG_KEY= 4096, + MODE_FLAG_USER_LIMIT = 8192, + + MODE_FLAG_HALFOP_PRIVILEGE = 16384, + + LAST_MODE_FLAG_ENUM +} IRCChannelModeFlags; + + +typedef struct +{ + IRCChannelModeFlags flags; + guint limit; + gchar *topic; + gchar *key; + guint topic_touched; + guint topic_toucher; +} IRCChannelModeState; + +#define TP_TYPE_PROPERTY_INFO_STRUCT (dbus_g_type_get_struct ("GValueArray", \ + G_TYPE_UINT, \ + G_TYPE_STRING, \ + G_TYPE_STRING, \ + G_TYPE_UINT, \ + G_TYPE_INVALID)) +#define TP_TYPE_PROPERTY_INFO_LIST (dbus_g_type_get_collection ("GPtrArray", \ + TP_TYPE_PROPERTY_INFO_STRUCT)) + +#define TP_TYPE_PROPERTY_VALUE_STRUCT (dbus_g_type_get_struct ("GValueArray", \ + G_TYPE_UINT, \ + G_TYPE_VALUE, \ + G_TYPE_INVALID)) +#define TP_TYPE_PROPERTY_VALUE_LIST (dbus_g_type_get_collection ("GPtrArray", \ + TP_TYPE_PROPERTY_VALUE_STRUCT)) + +#define TP_TYPE_PROPERTY_FLAGS_STRUCT (dbus_g_type_get_struct ("GValueArray", \ + G_TYPE_UINT, \ + G_TYPE_UINT, \ + G_TYPE_INVALID)) +#define TP_TYPE_PROPERTY_FLAGS_LIST (dbus_g_type_get_collection ("GPtrArray", \ + TP_TYPE_PROPERTY_FLAGS_STRUCT)) + +typedef enum +{ + TP_PROPERTY_INVITE_ONLY = 0, + TP_PROPERTY_LIMIT, + TP_PROPERTY_LIMITED, + TP_PROPERTY_MODERATED, + TP_PROPERTY_PASSWORD, + TP_PROPERTY_PASSWORD_REQUIRED, + TP_PROPERTY_PRIVATE, + TP_PROPERTY_SUBJECT, + TP_PROPERTY_SUBJECT_TIMESTAMP, + TP_PROPERTY_SUBJECT_CONTACT, + LAST_TP_PROPERTY_ENUM +} IdleMUCChannelTPProperty; + +typedef struct +{ + const gchar *name; + GType type; +} TPPropertySignature; + +typedef struct +{ + GValue *value; + guint flags; +} TPProperty; + + +static const TPPropertySignature property_signatures[] = +{ + {"invite-only", G_TYPE_BOOLEAN}, + {"limit", G_TYPE_UINT}, + {"limited", G_TYPE_BOOLEAN}, + {"moderated", G_TYPE_BOOLEAN}, + {"password", G_TYPE_STRING}, + {"password-required", G_TYPE_BOOLEAN}, + {"private", G_TYPE_BOOLEAN}, + {"subject", G_TYPE_STRING}, + {"subject-timestamp", G_TYPE_UINT}, + {"subject-contact", G_TYPE_UINT}, + {NULL, G_TYPE_NONE} +}; + +static const gchar *ascii_muc_states[] = +{ + "MUC_STATE_CREATED", + "MUC_STATE_JOINING", + "MUC_STATE_NEED_PASSWORD", + "MUC_STATE_JOINED", + "MUC_STATE_PARTED" +}; + +static void muc_channel_tp_properties_init(IdleMUCChannel *chan); +static void muc_channel_tp_properties_destroy(IdleMUCChannel *chan); +static void change_tp_properties(IdleMUCChannel *chan, const GPtrArray *props); +static void set_tp_property_flags(IdleMUCChannel *chan, const GArray *prop_ids, TpPropertyFlags add, TpPropertyFlags remove); + +static guint signals[LAST_SIGNAL] = {0}; + +/* private structure */ +typedef struct _IdleMUCChannelPrivate IdleMUCChannelPrivate; + +struct _IdleMUCChannelPrivate +{ + IdleConnection *connection; + gchar *object_path; + IdleHandle handle; + const gchar *channel_name; + + IdleHandle own_handle; + + guint recv_id; + GQueue *pending_messages; + + IdleHandleSet *local_pending; + IdleHandleSet *remote_pending; + IdleHandleSet *current_members; + + IdleMUCState state; + + IRCChannelModeState mode_state; + + guint group_flags; + guint password_flags; + TPProperty *properties; + + DBusGMethodInvocation *passwd_ctx; + + gboolean join_ready; + gboolean closed; + + gboolean dispose_has_run; +}; + +typedef struct _IdleMUCPendingMessage IdleMUCPendingMessage; + +struct _IdleMUCPendingMessage +{ + guint id; + + time_t timestamp; + IdleHandle sender; + + TpChannelTextMessageType type; + + gchar *text; +}; + +static void change_group_flags(IdleMUCChannel *chan, guint add, guint delete); +static void change_password_flags(IdleMUCChannel *chan, guint flag, gboolean state); + +#define _idle_muc_pending_new() \ + (g_slice_new(IdleMUCPendingMessage)) +#define _idle_muc_pending_new0() \ + (g_slice_new0(IdleMUCPendingMessage)) + +static void _idle_muc_pending_free(IdleMUCPendingMessage *msg) +{ + if (msg->text) + { + g_free(msg->text); + } + + g_slice_free(IdleMUCPendingMessage, msg); +} + +#define IDLE_MUC_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelPrivate)) + +static void +idle_muc_channel_init (IdleMUCChannel *obj) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (obj); + + priv->pending_messages = g_queue_new(); + + priv->group_flags = 0; /* | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE;*/ + + priv->password_flags = 0; + + priv->closed = FALSE; + priv->state = MUC_STATE_CREATED; + + priv->mode_state.flags = 0; + priv->mode_state.topic = NULL; + priv->mode_state.key = NULL; + + priv->properties = g_new0(TPProperty, LAST_TP_PROPERTY_ENUM); + muc_channel_tp_properties_init(obj); + + priv->dispose_has_run = FALSE; +} + +static void idle_muc_channel_dispose (GObject *object); +static void idle_muc_channel_finalize (GObject *object); + +static GObject *idle_muc_channel_constructor(GType type, guint n_props, GObjectConstructParam *props) +{ + GObject *obj; + IdleMUCChannelPrivate *priv; + DBusGConnection *bus; + IdleHandleStorage *handles; + gboolean valid; + + obj = G_OBJECT_CLASS(idle_muc_channel_parent_class)->constructor(type, n_props, props); + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(IDLE_MUC_CHANNEL(obj)); + + handles = _idle_connection_get_handles(priv->connection); + valid = idle_handle_ref(handles, TP_HANDLE_TYPE_ROOM, priv->handle); + g_assert(valid); + priv->channel_name = idle_handle_inspect(handles, TP_HANDLE_TYPE_ROOM, priv->handle); + + idle_connection_get_self_handle(priv->connection, &(priv->own_handle), NULL); + valid = idle_handle_ref(handles, TP_HANDLE_TYPE_CONTACT, priv->own_handle); + g_assert(valid); + + priv->local_pending = idle_handle_set_new(handles, TP_HANDLE_TYPE_CONTACT); + priv->remote_pending = idle_handle_set_new(handles, TP_HANDLE_TYPE_CONTACT); + priv->current_members = idle_handle_set_new(handles, TP_HANDLE_TYPE_CONTACT); + + bus = tp_get_bus(); + dbus_g_connection_register_g_object(bus, priv->object_path, obj); + g_assert(valid); + + return obj; +} + +static void idle_muc_channel_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + IdleMUCChannel *chan; + IdleMUCChannelPrivate *priv; + + g_assert(object != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(object)); + + chan = IDLE_MUC_CHANNEL(object); + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + + switch (property_id) + { + case PROP_CONNECTION: + { + g_value_set_object(value, priv->connection); + } + break; + case PROP_OBJECT_PATH: + { + g_value_set_string(value, priv->object_path); + } + break; + case PROP_CHANNEL_TYPE: + { + g_value_set_string(value, TP_IFACE_CHANNEL_TYPE_TEXT); + } + break; + case PROP_HANDLE_TYPE: + { + g_value_set_uint(value, TP_HANDLE_TYPE_ROOM); + } + break; + case PROP_HANDLE: + { + g_value_set_uint(value, priv->handle); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } + break; + } +} + +static void idle_muc_channel_set_property(GObject *object, guint property_id, const GValue *value, + GParamSpec *pspec) +{ + IdleMUCChannel *chan = IDLE_MUC_CHANNEL(object); + IdleMUCChannelPrivate *priv; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + switch (property_id) + { + case PROP_CONNECTION: + { + priv->connection = g_value_get_object(value); + } + break; + case PROP_OBJECT_PATH: + { + if (priv->object_path) + { + g_free(priv->object_path); + } + + priv->object_path = g_value_dup_string(value); + } + break; + case PROP_HANDLE: + { + priv->handle = g_value_get_uint(value); + g_debug("%s: setting handle to %u", G_STRFUNC, priv->handle); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + } + break; + } +} + +static void +idle_muc_channel_class_init (IdleMUCChannelClass *idle_muc_channel_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (idle_muc_channel_class); + GParamSpec *param_spec; + + g_type_class_add_private (idle_muc_channel_class, sizeof (IdleMUCChannelPrivate)); + + object_class->constructor = idle_muc_channel_constructor; + + object_class->get_property = idle_muc_channel_get_property; + object_class->set_property = idle_muc_channel_set_property; + + object_class->dispose = idle_muc_channel_dispose; + object_class->finalize = idle_muc_channel_finalize; + + param_spec = g_param_spec_object ("connection", "IdleConnection object", + "The IdleConnection object that owns this " + "MUCChannel object.", + IDLE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_string ("object-path", "D-Bus object path", + "The D-Bus object path used for this " + "object on the bus.", + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); + + param_spec = g_param_spec_string ("channel-type", "Telepathy channel type", + "The D-Bus interface representing the " + "type of this channel.", + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CHANNEL_TYPE, param_spec); + + param_spec = g_param_spec_uint ("handle-type", "Contact handle type", + "The TpHandleType representing a " + "room handle.", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HANDLE_TYPE, param_spec); + + param_spec = g_param_spec_uint ("handle", "Contact handle", + "The IdleHandle representing this room.", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HANDLE, param_spec); + + signals[CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[GROUP_FLAGS_CHANGED] = + g_signal_new ("group-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[LOST_MESSAGE] = + g_signal_new ("lost-message", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[MEMBERS_CHANGED] = + g_signal_new ("members-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__STRING_BOXED_BOXED_BOXED_BOXED_INT_INT, + G_TYPE_NONE, 7, G_TYPE_STRING, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY, DBUS_TYPE_G_UINT_ARRAY, G_TYPE_UINT, G_TYPE_UINT); + + signals[PASSWORD_FLAGS_CHANGED] = + g_signal_new ("password-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[PROPERTIES_CHANGED] = + g_signal_new ("properties-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_VALUE, G_TYPE_INVALID))))); + + signals[PROPERTY_FLAGS_CHANGED] = + g_signal_new ("property-flags-changed", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID))))); + + signals[RECEIVED] = + g_signal_new ("received", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_INT_INT_INT_STRING, + G_TYPE_NONE, 6, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SEND_ERROR] = + g_signal_new ("send-error", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_INT_STRING, + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[SENT] = + g_signal_new ("sent", + G_OBJECT_CLASS_TYPE (idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT_INT_STRING, + G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING); + + signals[JOIN_READY] = + g_signal_new("join-ready", + G_OBJECT_CLASS_TYPE(idle_muc_channel_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_muc_channel_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (idle_muc_channel_class), &dbus_glib_idle_muc_channel_object_info); +} + +void +idle_muc_channel_dispose (GObject *object) +{ + IdleMUCChannel *self = IDLE_MUC_CHANNEL (object); + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + if (!priv->closed) + { + g_signal_emit(self, signals[CLOSED], 0); + priv->closed = TRUE; + } + + if (G_OBJECT_CLASS (idle_muc_channel_parent_class)->dispose) + G_OBJECT_CLASS (idle_muc_channel_parent_class)->dispose (object); +} +#if 0 +/* unref all handles in given GArray */ +static void unref_handle_array(IdleHandleStorage *storage, GArray *handles, TpHandleType type) +{ + int i; + + g_assert(storage != NULL); + g_assert(handles != NULL); + + for (i=0; i<handles->len; i++) + { + idle_handle_unref(storage, type, (IdleHandle)(g_array_index(handles, guint, i))); + } +} +#endif + +#if 0 +static void handle_unref_foreach(IdleHandleSet *set, IdleHandle handle, gpointer userdata) +{ + IdleHandleStorage *storage = IDLE_HANDLE_STORAGE(userdata); + + idle_handle_unref(storage, TP_HANDLE_TYPE_CONTACT, handle); +} +#endif + +void +idle_muc_channel_finalize (GObject *object) +{ + IdleMUCChannel *self = IDLE_MUC_CHANNEL (object); + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE (self); + IdleHandleStorage *handles; + IdleMUCPendingMessage *msg; + + handles = _idle_connection_get_handles(priv->connection); + idle_handle_unref(handles, TP_HANDLE_TYPE_ROOM, priv->handle); + + idle_handle_unref(handles, TP_HANDLE_TYPE_CONTACT, priv->own_handle); + + if (priv->object_path) + { + g_free(priv->object_path); + } + + if (priv->mode_state.topic) + { + g_free(priv->mode_state.topic); + } + + if (priv->mode_state.key) + { + g_free(priv->mode_state.key); + } + + while ((msg = g_queue_pop_head(priv->pending_messages)) != NULL) + { + _idle_muc_pending_free(msg); + } + + g_queue_free(priv->pending_messages); + + muc_channel_tp_properties_destroy(self); + g_free(priv->properties); + + idle_handle_set_destroy(priv->current_members); + idle_handle_set_destroy(priv->local_pending); + idle_handle_set_destroy(priv->remote_pending); + + G_OBJECT_CLASS (idle_muc_channel_parent_class)->finalize (object); +} + +static void muc_channel_tp_properties_init(IdleMUCChannel *chan) +{ + IdleMUCChannelPrivate *priv; + TPProperty *props; + int i; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + props = priv->properties; + + for (i=0; i<LAST_TP_PROPERTY_ENUM; i++) + { + GValue *value; + props[i].value = value = g_new0(GValue, 1); + + g_value_init(value, property_signatures[i].type); + + props[i].flags = 0; + } +} + +static void muc_channel_tp_properties_destroy(IdleMUCChannel *chan) +{ + IdleMUCChannelPrivate *priv; + TPProperty *props; + int i; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + props = priv->properties; + + for (i=0; i<LAST_TP_PROPERTY_ENUM; i++) + { + g_value_unset(props[i].value); + g_free(props[i].value); + } +} + +static gboolean g_value_compare(const GValue *v1, const GValue *v2) +{ + GType t1, t2; + + g_assert(v1 != NULL); + g_assert(v2 != NULL); + + g_assert(G_IS_VALUE(v1)); + g_assert(G_IS_VALUE(v2)); + + t1 = G_VALUE_TYPE(v1); + t2 = G_VALUE_TYPE(v2); + + if (t1 != t2) + { + g_debug("%s: different types %s and %s compared!", G_STRFUNC, g_type_name(t1), g_type_name(t2)); + return FALSE; + } + + switch (t1) + { + case G_TYPE_BOOLEAN: + return g_value_get_boolean(v1) == g_value_get_boolean(v2); + case G_TYPE_UINT: + return g_value_get_uint(v1) == g_value_get_uint(v2); + case G_TYPE_STRING: + { + const gchar *s1, *s2; + + s1 = g_value_get_string(v1); + s2 = g_value_get_string(v2); + + if ((s1 == NULL) && (s2 == NULL)) + { + return TRUE; + } + else if ((s1 == NULL) || (s2 == NULL)) + { + return FALSE; + } + + return (strcmp(s1, s2) == 0); + } + default: + g_debug("%s: unknown type %s in comparison", G_STRFUNC, g_type_name(t1)); + return FALSE; + } +} + +static void change_tp_properties(IdleMUCChannel *chan, const GPtrArray *props) +{ + IdleMUCChannelPrivate *priv; + int i; + GPtrArray *changed_props; + GArray *flags; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + g_assert(props != NULL); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + changed_props = g_ptr_array_new(); + flags = g_array_new(FALSE, FALSE, sizeof(guint)); + + for (i=0; i<props->len; i++) + { + GValue *curr_val; + GValue prop = {0, }; + GValue *new_val; + guint prop_id; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_set_static_boxed(&prop, g_ptr_array_index(props, i)); + + dbus_g_type_struct_get(&prop, + 0, &prop_id, + 1, &new_val, + G_MAXUINT); + + if (prop_id >= LAST_TP_PROPERTY_ENUM) + { + g_debug("%s: prop_id >= LAST_TP_PROPERTY_ENUM, corruption!11", G_STRFUNC); + continue; + } + + curr_val = priv->properties[prop_id].value; + + if (!g_value_compare(new_val, curr_val)) + { + g_value_copy(new_val, curr_val); + + g_ptr_array_add(changed_props, g_value_get_boxed(&prop)); + g_array_append_val(flags, prop_id); + + g_debug("%s: tp_property %u changed", G_STRFUNC, prop_id); + } + + g_value_unset(&prop); + } + + if (changed_props->len > 0) + { + g_debug("%s: emitting PROPERTIES_CHANGED with %u properties", G_STRFUNC, changed_props->len); + g_signal_emit(chan, signals[PROPERTIES_CHANGED], 0, changed_props); + } + + if (flags->len > 0) + { + g_debug("%s: flagging properties as readable with %u props", G_STRFUNC, flags->len); + set_tp_property_flags(chan, flags, TP_PROPERTY_FLAG_READ, 0); + } + + g_ptr_array_free(changed_props, TRUE); + g_array_free(flags, TRUE); +} + +static void set_tp_property_flags(IdleMUCChannel *chan, const GArray *props, TpPropertyFlags add, TpPropertyFlags remove) +{ + IdleMUCChannelPrivate *priv; + int i; + GPtrArray *changed_props; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + changed_props = g_ptr_array_new(); + + if (props == NULL) + { + g_debug("%s: setting all flags with %u, %u", G_STRFUNC, add, remove); + + for (i=0; i<LAST_TP_PROPERTY_ENUM; i++) + { + guint curr_flags = priv->properties[i].flags; + guint flags = (curr_flags | add) & (~remove); + + if (curr_flags != flags) + { + GValue prop = {0, }; + + g_value_init(&prop, TP_TYPE_PROPERTY_FLAGS_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_FLAGS_STRUCT)); + + dbus_g_type_struct_set(&prop, + 0, i, + 1, flags, + G_MAXUINT); + + priv->properties[i].flags = flags; + + g_ptr_array_add(changed_props, g_value_get_boxed(&prop)); + } + } + } + else + { + for (i=0; i<props->len; i++) + { + guint prop_id = g_array_index(props, guint, i); + guint curr_flags = priv->properties[prop_id].flags; + guint flags = (curr_flags | add) & (~remove); + + if (curr_flags != flags) + { + GValue prop = {0, }; + + g_value_init(&prop, TP_TYPE_PROPERTY_FLAGS_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_FLAGS_STRUCT)); + + dbus_g_type_struct_set(&prop, + 0, prop_id, + 1, flags, + G_MAXUINT); + + priv->properties[prop_id].flags = flags; + + g_ptr_array_add(changed_props, g_value_get_boxed(&prop)); + } + } + } + + if (changed_props->len > 0) + { + g_debug("%s: emitting PROPERTY_FLAGS_CHANGED with %u properties", G_STRFUNC, changed_props->len); + g_signal_emit(chan, signals[PROPERTY_FLAGS_CHANGED], 0, changed_props); + } + + g_ptr_array_free(changed_props, TRUE); +} + +static void change_sets(IdleMUCChannel *obj, GIntSet *add_current, GIntSet *remove_current, GIntSet *add_local, GIntSet *remove_local, GIntSet *add_remote, GIntSet *remove_remote, IdleHandle actor, TpChannelGroupChangeReason reason); + +static void provide_password_reply(IdleMUCChannel *chan, gboolean success); + +static void provide_password_reply(IdleMUCChannel *chan, gboolean success) +{ + IdleMUCChannelPrivate *priv; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + if (priv->passwd_ctx != NULL) + { + dbus_g_method_return(priv->passwd_ctx, success); + priv->passwd_ctx = NULL; + } + else + { + g_debug("%s: don't have a ProvidePassword context to return with! (channel handle %u)", G_STRFUNC, priv->handle); + } + + if (success) + { + change_password_flags(chan, TP_CHANNEL_PASSWORD_FLAG_PROVIDE, 0); + } +} + +static void change_state(IdleMUCChannel *obj, IdleMUCState state) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if ((state > MUC_STATE_JOINING) && (!priv->join_ready)) + { + g_signal_emit(obj, signals[JOIN_READY], 0, MUC_CHANNEL_JOIN_ERROR_NONE); + priv->join_ready = TRUE; + } + + if (priv->state == MUC_STATE_NEED_PASSWORD && state == MUC_STATE_JOINED) + { + change_password_flags(obj, TP_CHANNEL_PASSWORD_FLAG_PROVIDE, FALSE); + provide_password_reply(obj, TRUE); + } + + if (priv->state == MUC_STATE_NEED_PASSWORD && state == MUC_STATE_NEED_PASSWORD) + { + provide_password_reply(obj, FALSE); + } + + if (priv->state < MUC_STATE_NEED_PASSWORD && state == MUC_STATE_NEED_PASSWORD) + { + change_password_flags(obj, TP_CHANNEL_PASSWORD_FLAG_PROVIDE, TRUE); + } + + priv->state = state; + + g_debug("%s: IdleMUCChannel %u changed to state %s", G_STRFUNC, priv->handle, ascii_muc_states[state]); +} + +static void change_group_flags(IdleMUCChannel *obj, guint add, guint remove) +{ + IdleMUCChannelPrivate *priv; + guint _add = 0, _remove = 0; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + _add = (~priv->group_flags) & add; + _remove = priv->group_flags & remove; + + priv->group_flags |= add; + priv->group_flags &= ~remove; + + if (_add | _remove) + { + g_signal_emit(obj, signals[GROUP_FLAGS_CHANGED], 0, add, remove); + g_debug("%s: emitting GROUP_FLAGS_CHANGED with %u %u", G_STRFUNC, add, remove); + } +} + +static IdleMUCChannelTPProperty to_prop_id(IRCChannelModeFlags flag) +{ + switch (flag) + { + case MODE_FLAG_INVITE_ONLY: + return TP_PROPERTY_INVITE_ONLY; + case MODE_FLAG_MODERATED: + return TP_PROPERTY_MODERATED; + case MODE_FLAG_PRIVATE: + case MODE_FLAG_SECRET: + return TP_PROPERTY_PRIVATE; + case MODE_FLAG_KEY: + return TP_PROPERTY_PASSWORD_REQUIRED; + case MODE_FLAG_USER_LIMIT: + return TP_PROPERTY_LIMITED; + default: + return LAST_TP_PROPERTY_ENUM; + } +} + +static void change_mode_state(IdleMUCChannel *obj, guint add, guint remove) +{ + IdleMUCChannelPrivate *priv; + IRCChannelModeFlags flags; + guint group_add = 0, group_remove = 0; + GPtrArray *tp_props_to_change; + guint prop_flags = 0; + guint combined; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + remove &= ~add; + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + flags = priv->mode_state.flags; + + tp_props_to_change = g_ptr_array_new(); + + g_debug("%s: got %x, %x", G_STRFUNC, add, remove); + + add &= ~flags; + remove &= flags; + + g_debug("%s: operation %x, %x", G_STRFUNC, add, remove); + + flags |= add; + flags &= ~remove; + + combined = add|remove; + + if (add & MODE_FLAG_INVITE_ONLY) + { + if (!(flags & (MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE))) + { + group_remove |= TP_CHANNEL_GROUP_FLAG_CAN_ADD; + } + } + else if (remove & MODE_FLAG_INVITE_ONLY) + { + group_add |= TP_CHANNEL_GROUP_FLAG_CAN_ADD; + } + + if (combined & (MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE)) + { + GArray *flags_to_change; + + static const guint flags_helper[] = + { + TP_PROPERTY_INVITE_ONLY, + TP_PROPERTY_LIMIT, + TP_PROPERTY_LIMITED, + TP_PROPERTY_MODERATED, + TP_PROPERTY_PASSWORD, + TP_PROPERTY_PASSWORD_REQUIRED, + TP_PROPERTY_PRIVATE, + TP_PROPERTY_SUBJECT, + LAST_TP_PROPERTY_ENUM + }; + + flags_to_change = g_array_new(FALSE, FALSE, sizeof(guint)); + + for (i=0; flags_helper[i] != LAST_TP_PROPERTY_ENUM; i++) + { + guint prop_id = flags_helper[i]; + g_array_append_val(flags_to_change, prop_id); + } + + prop_flags = TP_PROPERTY_FLAG_WRITE; + + if (add & (MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE)) + { + group_add |= TP_CHANNEL_GROUP_FLAG_CAN_ADD|TP_CHANNEL_GROUP_FLAG_CAN_REMOVE|TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE; + + set_tp_property_flags(obj, flags_to_change, prop_flags, 0); + } + else if (remove & (MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE)) + { + group_remove |= TP_CHANNEL_GROUP_FLAG_CAN_REMOVE|TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE; + + if (flags & MODE_FLAG_INVITE_ONLY) + { + group_remove |= TP_CHANNEL_GROUP_FLAG_CAN_ADD; + } + + set_tp_property_flags(obj, flags_to_change, 0, prop_flags); + } + } + + for (i = 1; i<LAST_MODE_FLAG_ENUM; i = (i << 1)) + { + if (combined & i) + { + IdleMUCChannelTPProperty tp_prop_id; + + tp_prop_id = to_prop_id(i); + + if (tp_prop_id < LAST_TP_PROPERTY_ENUM) + { + GValue prop = {0, }; + GValue val_auto_is_fine = {0, }; + GValue *val = &val_auto_is_fine; + GType type = property_signatures[tp_prop_id].type; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + g_value_init(val, type); + + if (type != G_TYPE_BOOLEAN) + { + g_debug("%s: type != G_TYPE_BOOLEAN for %u (modeflag %u), ignoring", G_STRFUNC, tp_prop_id, i); + continue; + } + + g_value_set_boolean(val, (add & i) ? TRUE : FALSE); + + dbus_g_type_struct_set(&prop, + 0, tp_prop_id, + 1, val, + G_MAXUINT); + + g_ptr_array_add(tp_props_to_change, g_value_get_boxed(&prop)); + + if (add & i) + { + GValue prop = {0, }; + GValue val_auto_is_fine = {0, }; + GValue *val = &val_auto_is_fine; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + if (i == MODE_FLAG_USER_LIMIT) + { + g_value_init(val, G_TYPE_UINT); + g_value_set_uint(val, priv->mode_state.limit); + tp_prop_id = TP_PROPERTY_LIMIT; + } + else if (i == MODE_FLAG_KEY) + { + g_value_init(val, G_TYPE_STRING); + g_value_set_string(val, priv->mode_state.key); + tp_prop_id = TP_PROPERTY_PASSWORD; + } + else + { + continue; + } + + dbus_g_type_struct_set(&prop, + 0, tp_prop_id, + 1, val, + G_MAXUINT); + + g_ptr_array_add(tp_props_to_change, g_value_get_boxed(&prop)); + } + } + } + } + + change_group_flags(obj, group_add, group_remove); + change_tp_properties(obj, tp_props_to_change); + + priv->mode_state.flags = flags; + + g_debug("%s: changed to %x", G_STRFUNC, flags); +} + +static void change_password_flags(IdleMUCChannel *obj, guint flag, gboolean state) +{ + IdleMUCChannelPrivate *priv; + guint add = 0, remove = 0; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if (state) + { + add = (~(priv->password_flags)) & flag; + priv->password_flags |= flag; + } + else + { + remove = priv->password_flags & flag; + priv->password_flags &= ~flag; + } + + if (add | remove) + { + g_debug("%s: emitting PASSWORD_FLAGS_CHANGED with %u %u", G_STRFUNC, add, remove); + g_signal_emit(obj, signals[PASSWORD_FLAGS_CHANGED], 0, add, remove); + } +} + +static void change_sets(IdleMUCChannel *obj, + GIntSet *add_current, + GIntSet *remove_current, + GIntSet *add_local, + GIntSet *remove_local, + GIntSet *add_remote, + GIntSet *remove_remote, + IdleHandle actor, + TpChannelGroupChangeReason reason) +{ + IdleMUCChannelPrivate *priv; + GIntSet *add, *remove, *local_pending, *remote_pending, *tmp1, *tmp2; + GArray *vadd, *vremove, *vlocal, *vremote; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + add = g_intset_new(); + remove = g_intset_new(); + local_pending = g_intset_new(); + remote_pending = g_intset_new(); + + if (add_current) + { + tmp1 = idle_handle_set_update(priv->current_members, add_current); + tmp2 = g_intset_union(add, tmp1); + + g_intset_destroy(add); + add = tmp2; + + g_intset_destroy(tmp1); + } + + if (remove_current) + { + tmp1 = idle_handle_set_difference_update(priv->current_members, remove_current); + tmp2 = g_intset_union(remove, tmp1); + + g_intset_destroy(remove); + remove = tmp2; + + g_intset_destroy(tmp1); + } + + if (add_local) + { + tmp1 = idle_handle_set_update(priv->local_pending, add_local); + tmp2 = g_intset_union(local_pending, tmp1); + + g_intset_destroy(local_pending); + local_pending = tmp2; + + g_intset_destroy(tmp1); + } + + if (remove_local) + { + tmp1 = idle_handle_set_difference_update(priv->local_pending, remove_local); + tmp2 = g_intset_union(remove, tmp1); + + g_intset_destroy(remove); + remove = tmp2; + + g_intset_destroy(tmp1); + } + + if (add_remote) + { + tmp1 = idle_handle_set_update(priv->remote_pending, add_remote); + tmp2 = g_intset_union(remote_pending, tmp1); + + g_intset_destroy(remote_pending); + remote_pending = tmp2; + + g_intset_destroy(tmp1); + } + + if (remove_remote) + { + tmp1 = idle_handle_set_difference_update(priv->remote_pending, remove_remote); + tmp2 = g_intset_union(remove, tmp1); + + g_intset_destroy(remove); + remove = tmp2; + + g_intset_destroy(tmp1); + } + + tmp1 = g_intset_difference(remove, add); + g_intset_destroy(remove); + remove = tmp1; + + tmp1 = g_intset_difference(remove, local_pending); + g_intset_destroy(remove); + remove = tmp1; + + tmp1 = g_intset_difference(remove, remote_pending); + g_intset_destroy(remove); + remove = tmp1; + + vadd = g_intset_to_array(add); + vremove = g_intset_to_array(remove); + vlocal = g_intset_to_array(local_pending); + vremote = g_intset_to_array(remote_pending); + + if ((vadd->len + vremove->len + vlocal->len + vremote->len) > 0) + { + g_debug("%s: emitting MEMBERS_CHANGED for channel with handle %u, amounts to (%u, %u, %u, %u)", G_STRFUNC, priv->handle, vadd->len, vremove->len, vlocal->len, vremote->len); + g_signal_emit(obj, signals[MEMBERS_CHANGED], 0, "", vadd, vremove, vlocal, vremote, actor, reason); + } + + g_array_free(vadd, TRUE); + g_array_free(vremove, TRUE); + g_array_free(vlocal, TRUE); + g_array_free(vremote, TRUE); + + g_intset_destroy(add); + g_intset_destroy(remove); + g_intset_destroy(local_pending); + g_intset_destroy(remote_pending); +} + +gboolean _idle_muc_channel_receive(IdleMUCChannel *chan, TpChannelTextMessageType type, IdleHandle sender, const gchar *text) +{ + IdleMUCChannelPrivate *priv; + IdleMUCPendingMessage *msg; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + msg = _idle_muc_pending_new(); + + msg->id = priv->recv_id++; + msg->timestamp = time(NULL); + msg->sender = sender; + msg->type = type; + msg->text = g_strdup(text); + + g_queue_push_tail(priv->pending_messages, msg); + + g_signal_emit(chan, signals[RECEIVED], 0, + msg->id, + msg->timestamp, + msg->sender, + msg->type, + 0, + msg->text); + + g_debug("%s: queued message %u", G_STRFUNC, msg->id); + + return FALSE; +} + +static void send_mode_query_request(IdleMUCChannel *chan) +{ + IdleMUCChannelPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+2]; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + g_snprintf(cmd, IRC_MSG_MAXLEN+2, "MODE %s", priv->channel_name); + + _idle_connection_send(priv->connection, cmd); +} + +void _idle_muc_channel_join(IdleMUCChannel *chan, const gchar *nick) +{ + IdleMUCChannelPrivate *priv; + IdleHandle handle; + GIntSet *set; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + g_assert(nick != NULL); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + set = g_intset_new(); + + handle = idle_handle_for_contact(_idle_connection_get_handles(priv->connection), nick); + + if (handle == 0) + { + g_debug("%s: invalid nick (%s)", G_STRFUNC, nick); + + return; + } + + if (handle == priv->own_handle) + { + /* woot we managed to get into a channel, great */ + change_state(chan, MUC_STATE_JOINED); + g_intset_add(set, handle); + change_sets(chan, set, NULL, NULL, set, NULL, set, handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + change_group_flags(chan, TP_CHANNEL_GROUP_FLAG_CAN_ADD, 0); + + send_mode_query_request(chan); + + if (priv->channel_name[0] == '+') + { + /* according to IRC specs, PLUS channels do not support channel modes and alway have only +t set, so we work with that. */ + change_mode_state(chan, MODE_FLAG_TOPIC_ONLY_SETTABLE_BY_OPS, 0); + } + } + else + { + g_intset_add(set, handle); + + change_sets(chan, set, NULL, NULL, NULL, NULL, set, handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + } + + g_debug("%s: member joined with handle %u and nick %s", G_STRFUNC, handle, nick); + + g_intset_destroy(set); +} + +void _idle_muc_channel_part(IdleMUCChannel *chan, const gchar *nick) +{ + return _idle_muc_channel_kick(chan, nick, nick, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); +} + +void _idle_muc_channel_kick(IdleMUCChannel *chan, const gchar *nick, const gchar *kicker, TpChannelGroupChangeReason reason) +{ + IdleMUCChannelPrivate *priv; + IdleHandle handle; + IdleHandle kicker_handle; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + handle = idle_handle_for_contact(_idle_connection_get_handles(priv->connection), nick); + + if (handle == 0) + { + g_debug("%s: failed to get handle for (%s)", G_STRFUNC, nick); + + return; + } + + kicker_handle = idle_handle_for_contact(_idle_connection_get_handles(priv->connection), kicker); + + if (kicker_handle == 0) + { + g_debug("%s: failed to get handle for (%s)", G_STRFUNC, kicker); + } + + return _idle_muc_channel_handle_quit(chan, + handle, + FALSE, + kicker_handle, + reason); +} + +void _idle_muc_channel_handle_quit(IdleMUCChannel *chan, + IdleHandle handle, + gboolean suppress, + IdleHandle actor, + TpChannelGroupChangeReason reason) +{ + IdleMUCChannelPrivate *priv; + GIntSet *set; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + set = g_intset_new(); + + g_intset_add(set, handle); + + change_sets(chan, NULL, set, NULL, set, NULL, set, actor, reason); + + if (handle == priv->own_handle) + { + g_debug("%s: it was us!", G_STRFUNC); + + change_state(chan, MUC_STATE_PARTED); + + if (!suppress) + { + priv->closed = TRUE; + + g_signal_emit(chan, signals[CLOSED], 0); + } + } + + g_intset_destroy(set); +} + +void _idle_muc_channel_invited(IdleMUCChannel *chan, IdleHandle inviter) +{ + IdleMUCChannelPrivate *priv; + GIntSet *handles_to_add; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + handles_to_add = g_intset_new(); + + g_intset_add(handles_to_add, priv->own_handle); + + change_sets(chan, NULL, NULL, handles_to_add, NULL, NULL, NULL, inviter, TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); + + g_intset_destroy(handles_to_add); +} + +void _idle_muc_channel_names(IdleMUCChannel *chan, GArray *names) +{ + IdleMUCChannelPrivate *priv; + int i; + GIntSet *handles_to_add; + IdleHandleStorage *handles; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + g_assert(names != NULL); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + handles = _idle_connection_get_handles(priv->connection); + + handles_to_add = g_intset_new(); + + for (i=0; i<names->len; i++) + { + IdleHandle handle; + gchar *nick = g_array_index(names, gchar *, i); + gunichar ucs4char; + + gchar own_mode = '\0'; + + ucs4char = g_utf8_get_char_validated(nick, -1); + + if (!g_unichar_isalpha(ucs4char)) + { + own_mode = *nick; + + nick++; + } + + handle = idle_handle_for_contact(handles, nick); + + if (handle == 0) + { + g_debug("%s: failed to get valid handle for nick %s, ignoring", G_STRFUNC, nick); + continue; + } + + if (handle == priv->own_handle) + { + guint remove = MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_VOICE_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE; + guint add = 0; + + switch (own_mode) + { + case '@': + { + g_debug("%s: we are OP", G_STRFUNC); + add |= MODE_FLAG_OPERATOR_PRIVILEGE; + } + break; + case '&': + { + g_debug("%s: we are HALFOP", G_STRFUNC); + add |= MODE_FLAG_HALFOP_PRIVILEGE; + } + break; + case '+': + { + g_debug("%s: we are VOICED", G_STRFUNC); + add |= MODE_FLAG_VOICE_PRIVILEGE; + } + break; + default: + { + g_debug("%s: we are NORMAL", G_STRFUNC); + } + break; + } + + remove &= ~add; + + change_mode_state(chan, add, remove); + } + + g_intset_add(handles_to_add, handle); + + } + + change_sets(chan, handles_to_add, NULL, NULL, handles_to_add, NULL, handles_to_add, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); +} + +void _idle_muc_channel_mode(IdleMUCChannel *chan, const gchar *mode_str) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + gchar **mode_argv; + gchar *operation; + gboolean remove; + guint mode_accum = 0; + guint limit = 0; + gchar *key = NULL; + const gchar *own_nick; + GArray *flags_to_change; + static const guint flags_helper[] = + { + TP_PROPERTY_INVITE_ONLY, + TP_PROPERTY_LIMITED, + TP_PROPERTY_MODERATED, + TP_PROPERTY_PASSWORD_REQUIRED, + TP_PROPERTY_PRIVATE, + LAST_TP_PROPERTY_ENUM + }; + + int i = 1; + + mode_argv = g_strsplit_set(mode_str, " ", -1); + + if (mode_argv[0] == NULL) + { + g_debug("%s: failed to parse (%s) to tokens", G_STRFUNC, mode_str); + goto cleanupl; + } + + operation = mode_argv[0]+1; + + if (mode_argv[0][0] == '+') + { + remove = FALSE; + } + else if (mode_argv[0][0] == '-') + { + remove = TRUE; + } + else + { + g_debug("%s: failed to decide whether to add or remove modes in (%s)", G_STRFUNC, mode_argv[0]); + goto cleanupl; + } + + own_nick = idle_handle_inspect(_idle_connection_get_handles(priv->connection), TP_HANDLE_TYPE_CONTACT, priv->own_handle); + + while (*operation != '\0') + { + switch (*operation) + { + case 'o': + { + if (g_strncasecmp(own_nick, mode_argv[i++], -1) == 0) + { + g_debug("%s: got MODE o concerning us", G_STRFUNC); + mode_accum |= MODE_FLAG_OPERATOR_PRIVILEGE; + } + } + break; + case 'h': + { + if (g_strncasecmp(own_nick, mode_argv[i++], -1) == 0) + { + g_debug("%s: got MODE h concerning us", G_STRFUNC); + mode_accum |= MODE_FLAG_HALFOP_PRIVILEGE; + } + } + break; + case 'v': + { + if (g_strncasecmp(own_nick, mode_argv[i++], -1) == 0) + { + g_debug("%s: got MODE v concerning us", G_STRFUNC); + mode_accum |= MODE_FLAG_VOICE_PRIVILEGE; + } + } + break; + case 'l': + { + limit = atoi(mode_argv[i++]); + g_debug("%s: got channel user limit %u", G_STRFUNC, limit); + mode_accum |= MODE_FLAG_USER_LIMIT; + } + break; + case 'k': + { + key = g_strdup(mode_argv[i++]); + g_debug("%s: got channel key %s", G_STRFUNC, key); + mode_accum |= MODE_FLAG_KEY; + } + break; + case 'a': + { + mode_accum |= MODE_FLAG_ANONYMOUS; + } + break; + case 'i': + { + mode_accum |= MODE_FLAG_INVITE_ONLY; + } + break; + case 'm': + { + mode_accum |= MODE_FLAG_MODERATED; + } + break; + case 'n': + { + mode_accum |= MODE_FLAG_NO_OUTSIDE_MESSAGES; + } + break; + case 'q': + { + mode_accum |= MODE_FLAG_QUIET; + } + break; + case 'p': + { + mode_accum |= MODE_FLAG_PRIVATE; + } + break; + case 's': + { + mode_accum |= MODE_FLAG_SECRET; + } + break; + case 'r': + { + mode_accum |= MODE_FLAG_SERVER_REOP; + } + break; + case 't': + { + mode_accum |= MODE_FLAG_TOPIC_ONLY_SETTABLE_BY_OPS; + } + break; + default: + { + g_debug("%s: did not understand mode identifier %c", G_STRFUNC, *operation); + } + break; + } + operation++; + } + + if (mode_accum & MODE_FLAG_KEY) + { + priv->mode_state.key = key; + } + if (mode_accum & MODE_FLAG_USER_LIMIT) + { + priv->mode_state.limit = limit; + } + + flags_to_change = g_array_new(FALSE, FALSE, sizeof(guint)); + + for (i=0; flags_helper[i] != LAST_TP_PROPERTY_ENUM; i++) + { + guint prop_id = flags_helper[i]; + g_array_append_val(flags_to_change, prop_id); + } + + set_tp_property_flags(chan, flags_to_change, TP_PROPERTY_FLAG_READ, 0); + + if (!remove) + { + change_mode_state(chan, mode_accum, 0); + } + else + { + change_mode_state(chan, 0, mode_accum); + } + +cleanupl: + + g_strfreev(mode_argv); +} + +void _idle_muc_channel_topic(IdleMUCChannel *chan, const char *topic) +{ + GValue prop = {0, }; + GValue val = {0, }; + GPtrArray *arr; + IdleMUCChannelPrivate *priv; + + g_assert(chan != NULL); + g_assert(topic != NULL); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + g_value_init(&val, G_TYPE_STRING); + g_value_set_string(&val, topic); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT, + 1, &val, + G_MAXUINT); + + arr = g_ptr_array_new(); + + g_ptr_array_add(arr, g_value_get_boxed(&prop)); + + change_tp_properties(chan, arr); + + g_ptr_array_free(arr, TRUE); +} + +void _idle_muc_channel_topic_touch(IdleMUCChannel *chan, const IdleHandle toucher, const guint timestamp) +{ + GValue prop = {0, }; + GValue val = {0, }; + GPtrArray *arr; + + arr = g_ptr_array_new(); + + g_assert(chan != NULL); + g_assert(toucher != 0); + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + g_value_init(&val, G_TYPE_UINT); + g_value_set_uint(&val, toucher); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT_CONTACT, + 1, &val, + G_MAXUINT); + + g_ptr_array_add(arr, g_value_get_boxed(&prop)); + + g_value_set_uint(&val, timestamp); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT_TIMESTAMP, + 1, &val, + G_MAXUINT); + + g_ptr_array_add(arr, g_value_get_boxed(&prop)); + + change_tp_properties(chan, arr); + + g_ptr_array_free(arr, TRUE); +} + +void _idle_muc_channel_topic_full(IdleMUCChannel *chan, const IdleHandle toucher, const guint timestamp, const gchar *topic) +{ + GValue prop = {0, }; + GValue val = {0, }; + GPtrArray *arr; + + arr = g_ptr_array_new(); + + g_assert(chan != NULL); + g_assert(toucher != 0); + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop, dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + g_value_init(&val, G_TYPE_UINT); + g_value_set_uint(&val, toucher); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT_CONTACT, + 1, &val, + G_MAXUINT); + + g_ptr_array_add(arr, g_value_get_boxed(&prop)); + + g_value_set_uint(&val, timestamp); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT_TIMESTAMP, + 1, &val, + G_MAXUINT); + + g_ptr_array_add(arr, g_value_get_boxed(&prop)); + + g_value_unset(&val); + g_value_init(&val, G_TYPE_STRING); + g_value_set_string(&val, topic); + + dbus_g_type_struct_set(&prop, + 0, TP_PROPERTY_SUBJECT, + 1, &val, + G_MAXUINT); + + change_tp_properties(chan, arr); + + g_ptr_array_free(arr, TRUE); +} + +void _idle_muc_channel_topic_unset(IdleMUCChannel *chan) +{ + GArray *arr = g_array_new(FALSE, FALSE, sizeof(guint)); + guint not_even_g_array_append_can_take_address_of_enumeration_constants_in_c; + guint *tmp = ¬_even_g_array_append_can_take_address_of_enumeration_constants_in_c; + + *tmp = TP_PROPERTY_SUBJECT; + g_array_append_val(arr, *tmp); + *tmp = TP_PROPERTY_SUBJECT_TIMESTAMP; + g_array_append_val(arr, *tmp); + *tmp = TP_PROPERTY_SUBJECT_CONTACT; + g_array_append_val(arr, *tmp); + + set_tp_property_flags(chan, arr, 0, TP_PROPERTY_FLAG_READ); + + g_array_free(arr, TRUE); +} + +void _idle_muc_channel_badchannelkey(IdleMUCChannel *chan) +{ + change_state(chan, MUC_STATE_NEED_PASSWORD); +} + +void _idle_muc_channel_join_error(IdleMUCChannel *chan, IdleMUCChannelJoinError err) +{ + IdleMUCChannelPrivate *priv; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + if (!priv->join_ready) + { + priv->join_ready = TRUE; + + g_signal_emit(chan, signals[JOIN_READY], 0, err); + } + else + { + g_debug("%s: already emitted JOIN_READY! (current err %u)", G_STRFUNC, err); + } +} + +void _idle_muc_channel_rename(IdleMUCChannel *chan, IdleHandle old, IdleHandle new) +{ + IdleMUCChannelPrivate *priv; + GIntSet *cadd, *cremove, *ladd, *lremove, *radd, *rremove; + + g_assert(chan != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(chan)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + cadd = g_intset_new(); + cremove = g_intset_new(); + ladd = g_intset_new(); + lremove = g_intset_new(); + radd = g_intset_new(); + rremove = g_intset_new(); + + if (priv->own_handle == old) + { + gboolean valid; + IdleHandleStorage *handles; + + handles = _idle_connection_get_handles(priv->connection); + + valid = idle_handle_unref(handles, TP_HANDLE_TYPE_CONTACT, old); + g_assert(valid); + + priv->own_handle = new; + + valid = idle_handle_ref(handles, TP_HANDLE_TYPE_CONTACT, new); + g_assert(valid); + + g_debug("%s: changed own_handle to %u", G_STRFUNC, new); + } + + if (idle_handle_set_contains(priv->current_members, old)) + { + g_intset_add(cadd, new); + g_intset_add(cremove, old); + } + else if (idle_handle_set_contains(priv->local_pending, old)) + { + g_intset_add(ladd, new); + g_intset_add(lremove, old); + } + else if (idle_handle_set_contains(priv->remote_pending, old)) + { + g_intset_add(radd, new); + g_intset_add(rremove, old); + } + + change_sets(chan, cadd, cremove, ladd, lremove, radd, rremove, new, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + g_intset_destroy(cadd); + g_intset_destroy(cremove); + g_intset_destroy(ladd); + g_intset_destroy(lremove); + g_intset_destroy(radd); + g_intset_destroy(rremove); +} + +static void send_join_request(IdleMUCChannel *obj, const gchar *password) +{ + IdleMUCChannelPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+1]; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if (password) + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "JOIN %s %s", priv->channel_name, password); + } + else + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "JOIN %s", priv->channel_name); + } + + _idle_connection_send(priv->connection, cmd); +} + +void _idle_muc_channel_join_attempt(IdleMUCChannel *obj) +{ + return send_join_request(obj, NULL); +} + +gboolean _idle_muc_channel_has_current_member(IdleMUCChannel *chan, IdleHandle handle) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE(chan); + + return idle_handle_set_contains(priv->current_members, handle); +} + +static gboolean send_invite_request(IdleMUCChannel *obj, IdleHandle handle, GError **error) +{ + IdleMUCChannelPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+1]; + const gchar *nick; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + nick = idle_handle_inspect(_idle_connection_get_handles(priv->connection), TP_HANDLE_TYPE_CONTACT, handle); + + if ((nick == NULL) || (nick[0] == '\0')) + { + g_debug("%s: invalid handle %u passed", G_STRFUNC, handle); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u passed", handle); + + return FALSE; + } + + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "INVITE %s %s", nick, priv->channel_name); + + _idle_connection_send(priv->connection, cmd); + + return TRUE; +} + +static gboolean send_kick_request(IdleMUCChannel *obj, IdleHandle handle, const gchar *msg, GError **error) +{ + IdleMUCChannelPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+1]; + const gchar *nick; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + nick = idle_handle_inspect(_idle_connection_get_handles(priv->connection), TP_HANDLE_TYPE_CONTACT, handle); + + if ((nick == NULL) || (nick[0] == '\0')) + { + g_debug("%s: invalid handle %u passed", G_STRFUNC, handle); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidHandle, "invalid handle %u passed", handle); + + return FALSE; + } + + if (msg != NULL) + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "KICK %s %s %s", priv->channel_name, nick, msg); + } + else + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "KICK %s %s", priv->channel_name, nick); + } + + _idle_connection_send(priv->connection, cmd); + + return TRUE; +} + +static gboolean add_member(IdleMUCChannel *obj, IdleHandle handle, GError **error) +{ + IdleMUCChannelPrivate *priv; + IdleHandleStorage *handles; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + handles = _idle_connection_get_handles(priv->connection); + + if (handle == priv->own_handle) + { + if (idle_handle_set_contains(priv->current_members, handle) || idle_handle_set_contains(priv->remote_pending, handle)) + { + g_debug("%s: we are already a member of or trying to join the channel with handle %u", G_STRFUNC, priv->handle); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "we are already a member of or trying to join the channel with handle %u", priv->handle); + + return FALSE; + } + else + { + GIntSet *add_set = g_intset_new(); + + send_join_request(obj, NULL); + + change_state(obj, MUC_STATE_JOINING); + + g_intset_add(add_set, handle); + + change_sets(obj, NULL, NULL, NULL, NULL, add_set, NULL, 0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + } + } + else + { + if (idle_handle_set_contains(priv->current_members, handle) || idle_handle_set_contains(priv->remote_pending, handle)) + { + g_debug("%s: the requested contact (handle %u) to be added to the room (handle %u) is already a member of or has already been invited to join the room", G_STRFUNC, handle, priv->handle); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "the requested contact (handle %u) to be added to the room (handle %u) is already a member of or has already been invited to join the room", handle, priv->handle); + + return FALSE; + } + else + { + GError *invite_error; + GIntSet *add_set = g_intset_new(); + + if (!send_invite_request(obj, handle, &invite_error)) + { + *error = invite_error; + + return FALSE; + } + + g_intset_add(add_set, handle); + + change_sets(obj, + NULL, NULL, + NULL, NULL, + add_set, NULL, + priv->own_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); + } + } + + return TRUE; +} + +static gint idle_pending_message_compare(gconstpointer msg, gconstpointer id) +{ + IdleMUCPendingMessage *message = (IdleMUCPendingMessage *)(msg); + + return (message->id != GPOINTER_TO_INT(id)); +} + +/** + * idle_muc_channel_acknowledge_pending_messages + * + * Implements DBus method AcknowledgePendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_acknowledge_pending_messages (IdleMUCChannel *obj, + const GArray *ids, + GError **error) +{ + IdleMUCChannelPrivate *priv; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + for (i=0; i<ids->len; i++) + { + GList *node; + IdleMUCPendingMessage *msg; + guint id = g_array_index(ids, guint, i); + + node = g_queue_find_custom(priv->pending_messages, + GINT_TO_POINTER(id), + idle_pending_message_compare); + + if (node == NULL) + { + g_debug("%s: message %u not found", G_STRFUNC, id); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "message id %u not found", id); + + return FALSE; + } + + msg = (IdleMUCPendingMessage *)(node->data); + + g_debug("%s: acknowledging pending message with id %u", G_STRFUNC, id); + + g_queue_delete_link(priv->pending_messages, node); + + _idle_muc_pending_free(msg); + } + + return TRUE; +} + +/** + * idle_muc_channel_add_members + * + * Implements DBus method AddMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_add_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error) +{ + IdleMUCChannelPrivate *priv; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + for (i=0; i < contacts->len; i++) + { + IdleHandle handle; + GError *add_error; + + handle = (IdleHandle)(g_array_index(contacts, guint, i)); + + if (!add_member(obj, handle, &add_error)) + { + *error = add_error; + + return FALSE; + } + } + + return TRUE; +} + +static void part_from_channel(IdleMUCChannel *obj, const gchar *msg) +{ + IdleMUCChannelPrivate *priv; + gchar cmd[IRC_MSG_MAXLEN+1]; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if (msg != NULL) + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "PART %s %s", priv->channel_name, msg); + } + else + { + g_snprintf(cmd, IRC_MSG_MAXLEN+1, "PART %s", priv->channel_name); + } + + _idle_connection_send(priv->connection, cmd); +} + + +/** + * idle_muc_channel_close + * + * Implements DBus method Close + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_close (IdleMUCChannel *obj, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if (priv->state == MUC_STATE_JOINED) + { + part_from_channel(obj, NULL); + } + + if (priv->state < MUC_STATE_JOINED) + { + if (!priv->closed) + { + g_signal_emit(obj, signals[CLOSED], 0); + priv->closed = TRUE; + } + } + + g_debug("%s: called on %p", G_STRFUNC, obj); + + return TRUE; +} + +/** + * idle_muc_channel_get_all_members + * + * Implements DBus method GetAllMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_all_members (IdleMUCChannel *obj, GArray ** ret, GArray ** ret1, GArray ** ret2, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = idle_handle_set_to_array(priv->current_members); + *ret1 = idle_handle_set_to_array(priv->local_pending); + *ret2 = idle_handle_set_to_array(priv->remote_pending); + + return TRUE; +} + + +/** + * idle_muc_channel_get_channel_type + * + * Implements DBus method GetChannelType + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_channel_type (IdleMUCChannel *obj, gchar ** ret, GError **error) +{ + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + *ret = g_strdup(TP_IFACE_CHANNEL_TYPE_TEXT); + + return TRUE; +} + + +/** + * idle_muc_channel_get_group_flags + * + * Implements DBus method GetGroupFlags + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_group_flags (IdleMUCChannel *obj, guint* ret, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = priv->group_flags; + + return TRUE; +} + + +/** + * idle_muc_channel_get_handle + * + * Implements DBus method GetHandle + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_handle (IdleMUCChannel *obj, guint* ret, guint* ret1, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = TP_HANDLE_TYPE_ROOM; + *ret1 = priv->handle; + + g_debug("%s: returning handle %u", G_STRFUNC, *ret1); + + return TRUE; +} + + +/** + * idle_muc_channel_get_handle_owners + * + * Implements DBus method GetHandleOwners + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_handle_owners (IdleMUCChannel *obj, const GArray * handles, GArray ** ret, GError **error) +{ + int i; + + *ret = g_array_sized_new(FALSE, FALSE, sizeof(guint), handles->len); + + for (i=0; i<handles->len; i++) + { + g_array_index(*ret, guint, i) = g_array_index(handles, guint, i); + } + + return TRUE; +} + + +/** + * idle_muc_channel_get_interfaces + * + * Implements DBus method GetInterfaces + * on interface org.freedesktop.Telepathy.Channel + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_interfaces (IdleMUCChannel *obj, gchar *** ret, GError **error) +{ + /* TODO implement Properties */ + const gchar *interfaces[] = {TP_IFACE_CHANNEL_INTERFACE_PASSWORD, + TP_IFACE_CHANNEL_INTERFACE_GROUP, + TP_IFACE_PROPERTIES, NULL}; + + *ret = g_strdupv((gchar **)(interfaces)); + + return TRUE; +} + + +/** + * idle_muc_channel_get_local_pending_members + * + * Implements DBus method GetLocalPendingMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_local_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = idle_handle_set_to_array(priv->local_pending); + + return TRUE; +} + + +/** + * idle_muc_channel_get_members + * + * Implements DBus method GetMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = idle_handle_set_to_array(priv->current_members); + + return TRUE; +} + + +/** + * idle_muc_channel_get_message_types + * + * Implements DBus method GetMessageTypes + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_message_types (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + int i; + + *ret = g_array_sized_new(FALSE, FALSE, sizeof(guint), 3); + + for (i=0; i<3; i++) + { + g_array_index(*ret, guint, i) = i; + } + + return TRUE; +} + + +/** + * idle_muc_channel_get_password_flags + * + * Implements DBus method GetPasswordFlags + * on interface org.freedesktop.Telepathy.Channel.Interface.Password + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_password_flags (IdleMUCChannel *obj, guint* ret, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = priv->password_flags; + + return TRUE; +} + + +/** + * idle_muc_channel_get_properties + * + * Implements DBus method GetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_properties (IdleMUCChannel *obj, const GArray * properties, GPtrArray ** ret, GError **error) +{ + IdleMUCChannelPrivate *priv; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + for (i=0; i<properties->len; i++) + { + IdleMUCChannelTPProperty prop = g_array_index(properties, guint, i); + + if (prop >= LAST_TP_PROPERTY_ENUM) + { + g_debug("%s: invalid property id %u", G_STRFUNC, prop); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid property id %u", prop); + + return FALSE; + } + + if (!(priv->properties[prop].flags & TP_PROPERTY_FLAG_READ)) + { + g_debug("%s: not allowed to read property %u", G_STRFUNC, prop); + + *error = g_error_new(TELEPATHY_ERRORS, PermissionDenied, "not allowed to read property %u", prop); + + return FALSE;; + } + } + + *ret = g_ptr_array_sized_new(properties->len); + + for (i=0; i<properties->len; i++) + { + IdleMUCChannelTPProperty prop = g_array_index(properties, guint, i); + GValue prop_val = {0, }; + + g_value_init(&prop_val, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_take_boxed(&prop_val, + dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_VALUE_STRUCT)); + + dbus_g_type_struct_set(&prop_val, + 0, prop, + 1, priv->properties[prop].value, + G_MAXUINT); + + g_ptr_array_add(*ret, g_value_get_boxed(&prop_val)); + } + + return TRUE; +} + + +/** + * idle_muc_channel_get_remote_pending_members + * + * Implements DBus method GetRemotePendingMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_remote_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error) +{ + IdleMUCChannelPrivate *priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = idle_handle_set_to_array(priv->remote_pending); + + return TRUE; +} + + +/** + * idle_muc_channel_get_self_handle + * + * Implements DBus method GetSelfHandle + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_get_self_handle (IdleMUCChannel *obj, guint* ret, GError **error) +{ + IdleMUCChannelPrivate *priv; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + g_debug("%s: returning handle %u", G_STRFUNC, priv->own_handle); + + *ret = priv->own_handle; + + return TRUE; +} + + +/** + * idle_muc_channel_list_pending_messages + * + * Implements DBus method ListPendingMessages + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_list_pending_messages (IdleMUCChannel *obj, + gboolean clear, + GPtrArray ** ret, + GError **error) +{ + IdleMUCChannelPrivate *priv; + guint count; + GPtrArray *messages; + GList *cur; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + count = g_queue_get_length(priv->pending_messages); + + messages = g_ptr_array_sized_new(count); + + for (cur = g_queue_peek_head_link(priv->pending_messages); cur != NULL; cur = cur->next) + { + IdleMUCPendingMessage *msg = (IdleMUCPendingMessage *)(cur->data); + GValueArray *vals; + + vals = g_value_array_new(6); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 0), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 0), msg->id); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 1), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 1), msg->timestamp); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 2), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 2), msg->sender); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 3), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 3), msg->type); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 4), G_TYPE_UINT); + g_value_set_uint(g_value_array_get_nth(vals, 4), 0); + + g_value_array_append(vals, NULL); + g_value_init(g_value_array_get_nth(vals, 5), G_TYPE_STRING); + g_value_set_string(g_value_array_get_nth(vals, 5), msg->text); + + g_ptr_array_add(messages, vals); + } + + *ret = messages; + + if (clear) + { + IdleMUCPendingMessage *msg; + + while ((msg = g_queue_pop_head(priv->pending_messages)) != NULL) + { + _idle_muc_pending_free(msg); + } + } + + return TRUE; +} + + +/** + * idle_muc_channel_list_properties + * + * Implements DBus method ListProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_list_properties (IdleMUCChannel *obj, GPtrArray ** ret, GError **error) +{ + IdleMUCChannelPrivate *priv; + guint i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + *ret = g_ptr_array_sized_new(LAST_TP_PROPERTY_ENUM); + + for (i=0; i<LAST_TP_PROPERTY_ENUM; i++) + { + GValue prop = {0, }; + const gchar *dbus_sig; + const gchar *name; + guint flags; + + switch (property_signatures[i].type) + { + case G_TYPE_BOOLEAN: + { + dbus_sig = "b"; + } + break; + case G_TYPE_UINT: + { + dbus_sig = "u"; + } + break; + case G_TYPE_STRING: + { + dbus_sig = "s"; + } + break; + default: + { + g_debug("%s: encountered unknown type %s", G_STRFUNC, g_type_name(property_signatures[i].type)); + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "internal error in %s", G_STRFUNC); + + return FALSE; + } + break; + } + + g_value_init(&prop, TP_TYPE_PROPERTY_INFO_STRUCT); + g_value_take_boxed(&prop, + dbus_g_type_specialized_construct(TP_TYPE_PROPERTY_INFO_STRUCT)); + + name = property_signatures[i].name; + flags = priv->properties[i].flags; + + dbus_g_type_struct_set(&prop, + 0, i, + 1, property_signatures[i].name, + 2, dbus_sig, + 3, priv->properties[i].flags, + G_MAXUINT); + + g_ptr_array_add(*ret, g_value_get_boxed(&prop)); + } + + return TRUE; +} + + +/** + * idle_muc_channel_provide_password + * + * Implements DBus method ProvidePassword + * on interface org.freedesktop.Telepathy.Channel.Interface.Password + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_provide_password (IdleMUCChannel *obj, const gchar * password, DBusGMethodInvocation *context) +{ + IdleMUCChannelPrivate *priv; + GError *error; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + if (!(priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE) || (priv->passwd_ctx != NULL)) + { + g_debug("%s: don't need a password now or authentication already in process (handle %u)", G_STRFUNC, priv->handle); + + error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "don't need a password now or authentication already in process (handle %u)", priv->handle); + + dbus_g_method_return_error(context, error); + g_error_free(error); + + return FALSE; + } + + priv->passwd_ctx = context; + + send_join_request(obj, password); + + return TRUE; +} + + +/** + * idle_muc_channel_remove_members + * + * Implements DBus method RemoveMembers + * on interface org.freedesktop.Telepathy.Channel.Interface.Group + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_remove_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error) +{ + IdleMUCChannelPrivate *priv; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + for (i=0; i<contacts->len; i++) + { + GError *kick_error; + IdleHandle handle = g_array_index(contacts, guint, i); + + if (handle == priv->own_handle) + { + part_from_channel(obj, message); + + return TRUE; + } + + if (!idle_handle_set_contains(priv->current_members, handle)) + { + g_debug("%s: handle %u not a current member!", G_STRFUNC, handle); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "handle %u is not a current member of the channel", handle); + + return FALSE; + } + + if (!send_kick_request(obj, handle, message, &kick_error)) + { + g_debug("%s: send_kick_request failed: %s", G_STRFUNC, kick_error->message); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, kick_error->message); + + g_error_free(kick_error); + + return FALSE; + } + } + + return TRUE; +} + + +/** + * idle_muc_channel_send + * + * Implements DBus method Send + * on interface org.freedesktop.Telepathy.Channel.Type.Text + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_send (IdleMUCChannel *obj, guint type, const gchar * text, GError **error) +{ + IdleMUCChannelPrivate *priv; + gchar msg[IRC_MSG_MAXLEN+2]; + const char *recipient; + time_t timestamp; + const gchar *final_text = text; + gsize len; + gchar *part; + gsize headerlen; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + recipient = idle_handle_inspect(_idle_connection_get_handles(priv->connection), TP_HANDLE_TYPE_ROOM, priv->handle); + + if ((recipient == NULL) || (recipient[0] == '\0')) + { + g_debug("%s: invalid recipient (handle %u)", G_STRFUNC, priv->handle); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "invalid recipient"); + + return FALSE; + } + + len = strlen(final_text); + part = (gchar*)final_text; + + switch (type) + { + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL: + { + g_snprintf(msg, IRC_MSG_MAXLEN, "PRIVMSG %s :", recipient); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION: + { + g_snprintf(msg, IRC_MSG_MAXLEN, "PRIVMSG %s :\001ACTION ", recipient); + } + break; + case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE: + { + g_snprintf(msg, IRC_MSG_MAXLEN, "NOTICE %s :", recipient); + } + break; + default: + { + g_debug("%s: invalid message type %u", G_STRFUNC, type); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid message type %u", type); + + return FALSE; + } + break; + } + + headerlen = strlen(msg); + + while (part < final_text+len) + { + char *br = strchr (part, '\n'); + size_t len = IRC_MSG_MAXLEN-headerlen; + if (br) + { + len = (len < br - part) ? len : br - part; + } + + if (type == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) + { + g_snprintf(msg+headerlen, len + 1, "%s\001", part); + len -= 1; + } + else + { + g_strlcpy(msg+headerlen, part, len + 1); + } + part += len; + if (br) + { + part++; + } + + _idle_connection_send(priv->connection, msg); + } + + timestamp = time(NULL); + + if ((priv->mode_state.flags & MODE_FLAG_MODERATED) && !(priv->mode_state.flags & (MODE_FLAG_OPERATOR_PRIVILEGE|MODE_FLAG_HALFOP_PRIVILEGE|MODE_FLAG_VOICE_PRIVILEGE))) + { + g_debug("%s: emitting SEND_ERROR with (%u, %llu, %u, %s)", G_STRFUNC, TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED, (guint64)(timestamp), type, text); + g_signal_emit(obj, signals[SEND_ERROR], 0, TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED, timestamp, type, text); + } + else + { + g_debug("%s: emitting SENT with (%llu, %u, %s)", G_STRFUNC, (guint64)(timestamp), type, text); + g_signal_emit(obj, signals[SENT], 0, timestamp, type, text); + } + + return TRUE; +} + +static char to_irc_mode(IdleMUCChannelTPProperty prop_id) +{ + switch (prop_id) + { + case TP_PROPERTY_INVITE_ONLY: + return 'i'; + case TP_PROPERTY_MODERATED: + return 'm'; + case TP_PROPERTY_PRIVATE: + return 's'; + default: + return '\0'; + } +} + +static int prop_arr_find(const GPtrArray *props, IdleMUCChannelTPProperty needle) +{ + int i; + + for (i=0; i<props->len; i++) + { + GValue prop = {0, }; + guint prop_id; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_set_static_boxed(&prop, g_ptr_array_index(props, i)); + + dbus_g_type_struct_get(&prop, + 0, &prop_id, + G_MAXUINT); + + if (prop_id == needle) + { + return i; + } + } + + return -1; +} + +static void send_properties_request(IdleMUCChannel *obj, const GPtrArray *properties) +{ + IdleMUCChannelPrivate *priv; + int i; + GPtrArray *waiting; + gchar cmd[IRC_MSG_MAXLEN+2]; + size_t len; + gchar *body; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + g_assert(properties != NULL); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + waiting = g_ptr_array_new(); + + g_snprintf(cmd, IRC_MSG_MAXLEN+2, "MODE %s ", priv->channel_name); + len = strlen(cmd); + body = cmd+len; + + for (i=0; i<properties->len; i++) + { + GValue prop = {0, }; + IdleMUCChannelTPProperty prop_id; + GValue *prop_val; + char irc_mode; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_set_static_boxed(&prop, g_ptr_array_index(properties, i)); + + dbus_g_type_struct_get(&prop, + 0, &prop_id, + 1, &prop_val, + G_MAXUINT); + + irc_mode = to_irc_mode(prop_id); + + if (irc_mode != '\0') + { + g_assert(G_VALUE_TYPE(prop_val) == G_TYPE_BOOLEAN); + + gboolean state = g_value_get_boolean(prop_val); + + size_t seq = 0; + + if (state) + { + body[seq++] = '+'; + } + else + { + body[seq++] = '-'; + } + + body[seq++] = irc_mode; + body[seq++] = '\0'; + + _idle_connection_send(priv->connection, cmd); + } + else + { + if (prop_id == TP_PROPERTY_SUBJECT) + { + const gchar *subject = g_value_get_string(prop_val); + gchar cmd[IRC_MSG_MAXLEN+2]; + + g_snprintf(cmd, IRC_MSG_MAXLEN+2, "TOPIC %s :%s", priv->channel_name, subject); + + _idle_connection_send(priv->connection, cmd); + } + else + { + g_ptr_array_add(waiting, g_value_get_boxed(&prop)); + } + } + } + + if (waiting->len) + { + int i, j; + gpointer tmp; + + i = prop_arr_find(waiting, TP_PROPERTY_LIMITED); + j = prop_arr_find(waiting, TP_PROPERTY_LIMIT); + + if ((i != -1) && (j != -1) && (i < j)) + { + g_debug("%s: swapping order of TP_PROPERTY_LIMIT and TP_PROPERTY_LIMITED", G_STRFUNC); + + tmp = g_ptr_array_index(waiting, i); + g_ptr_array_index(waiting, i) = g_ptr_array_index(waiting, j); + g_ptr_array_index(waiting, j) = tmp; + } + + i = prop_arr_find(waiting, TP_PROPERTY_PASSWORD_REQUIRED); + j = prop_arr_find(waiting, TP_PROPERTY_PASSWORD); + + if ((i != -1) && (j != -1) && (i < j)) + { + g_debug("%s: swapping order of TP_PROPERTY_PASSWORD and TP_PROPERTY_PASSWORD_REQUIRED", G_STRFUNC); + + tmp = g_ptr_array_index(waiting, i); + g_ptr_array_index(waiting, i) = g_ptr_array_index(waiting, j); + g_ptr_array_index(waiting, j) = tmp; + } + } + + /* okay now the data is ALWAYS before the boolean */ + + for (i=0; i<waiting->len; i++) + { + GValue prop = {0, }; + IdleMUCChannelTPProperty prop_id; + GValue *prop_val; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_set_static_boxed(&prop, g_ptr_array_index(waiting, i)); + + dbus_g_type_struct_get(&prop, + 0, &prop_id, + 1, &prop_val, + G_MAXUINT); + + g_assert(prop_id < LAST_TP_PROPERTY_ENUM); + + if (prop_id == TP_PROPERTY_LIMIT || prop_id == TP_PROPERTY_PASSWORD) + { + int j; + + g_value_copy(prop_val, priv->properties[prop_id].value); + + j = prop_arr_find(waiting, prop_id+1); + + if (j == -1) + { + if (prop_id == TP_PROPERTY_LIMIT && priv->mode_state.flags & MODE_FLAG_USER_LIMIT) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "+l %u", g_value_get_uint(prop_val)); + } + else if (prop_id == TP_PROPERTY_PASSWORD && priv->mode_state.flags & MODE_FLAG_KEY) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "+k %s", g_value_get_string(prop_val)); + } + else + { + g_debug("%s: %u", G_STRFUNC, __LINE__); + } + } + else + { + g_debug("%s: %u", G_STRFUNC, __LINE__); + } + } + else if (prop_id == TP_PROPERTY_LIMITED) + { + guint limit = g_value_get_uint(priv->properties[TP_PROPERTY_LIMIT].value); + + if (g_value_get_boolean(prop_val)) + { + if (limit != 0) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "+l %u", limit); + } + else + { + g_debug("%s: %u", G_STRFUNC, __LINE__); + } + } + else + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "-l"); + } + } + else if (prop_id == TP_PROPERTY_PASSWORD_REQUIRED) + { + const gchar *key = g_value_get_string(priv->properties[TP_PROPERTY_PASSWORD].value); + + if (g_value_get_boolean(prop_val)) + { + if (key != NULL) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "+k %s", key); + } + else + { + g_debug("%s: %u", G_STRFUNC, __LINE__); + } + } + else + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "-k"); + } + } + else + { + g_debug("%s: %u", G_STRFUNC, __LINE__); + } + + _idle_connection_send(priv->connection, cmd); + +#if 0 + if (prop_id == TP_PROPERTY_LIMIT + && (prop_arr_has_set(waiting, TP_PROPERTY_LIMITED) + || (priv->mode_state.flags & MODE_FLAG_USER_LIMIT))) + { + guint limit = g_value_get_uint(prop_val); + + g_snprintf(body, IRC_MSG_MAXLEN-len, "+l %u", limit); + } + else if (prop_id == TP_PROPERTY_PASSWORD + && (prop_arr_has_set(waiting, TP_PROPERTY_PASSWORD_REQUIRED) + || priv->mode_state.flags & MODE_FLAG_KEY)) + { + const gchar *key = g_value_get_string(prop_val); + + g_snprintf(body, IRC_MSG_MAXLEN-len, "+k %s", key); + } + else + { + if (prop_id == TP_PROPERTY_LIMITED && !g_value_get_boolean(prop_val)) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "-l"); + } + else if (prop_id == TP_PROPERTY_PASSWORD_REQUIRED && !g_value_get_boolean(prop_val)) + { + g_snprintf(body, IRC_MSG_MAXLEN-len, "-k"); + } + else + { + g_debug("%s: did not do anything with %u", G_STRFUNC, prop_id); + continue; + } + } +#endif + } + + g_ptr_array_free(waiting, TRUE); +} + +/** + * idle_muc_channel_set_properties + * + * Implements DBus method SetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occured, DBus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean idle_muc_channel_set_properties (IdleMUCChannel *obj, const GPtrArray * properties, GError **error) +{ + IdleMUCChannelPrivate *priv; + GPtrArray *to_change; + int i; + + g_assert(obj != NULL); + g_assert(IDLE_IS_MUC_CHANNEL(obj)); + + priv = IDLE_MUC_CHANNEL_GET_PRIVATE(obj); + + to_change = g_ptr_array_new(); + + for (i=0; i<properties->len; i++) + { + GValue prop = {0, }; + IdleMUCChannelTPProperty prop_id; + GValue *prop_val; + + g_value_init(&prop, TP_TYPE_PROPERTY_VALUE_STRUCT); + g_value_set_static_boxed(&prop, g_ptr_array_index(properties, i)); + + dbus_g_type_struct_get(&prop, + 0, &prop_id, + 1, &prop_val, + G_MAXUINT); + + if (prop_id >= LAST_TP_PROPERTY_ENUM) + { + g_debug("%s: invalid property id %u", G_STRFUNC, prop_id); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "invalid property id %u", prop_id); + + return FALSE; + } + + if ((priv->properties[prop_id].flags & TP_PROPERTY_FLAG_WRITE) == 0) + { + g_debug("%s: not allowed to set property with id %u", G_STRFUNC, prop_id); + + *error = g_error_new(TELEPATHY_ERRORS, PermissionDenied, "not allowed to set property with id %u", prop_id); + + return FALSE; + } + + if (!g_value_type_compatible(G_VALUE_TYPE(prop_val), property_signatures[prop_id].type)) + { + g_debug("%s: incompatible value type %s for prop_id %u", G_STRFUNC, g_type_name(G_VALUE_TYPE(prop_val)), prop_id); + + *error = g_error_new(TELEPATHY_ERRORS, InvalidArgument, "incompatible value type %s for prop_id %u", g_type_name(G_VALUE_TYPE(prop_val)), prop_id); + + return FALSE; + } + + if (!g_value_compare(prop_val, priv->properties[prop_id].value)) + { + g_ptr_array_add(to_change, g_value_get_boxed(&prop)); + } + } + + send_properties_request(obj, to_change); + + g_ptr_array_free(to_change, TRUE); + + return TRUE; +} + diff --git a/src/idle-muc-channel.h b/src/idle-muc-channel.h new file mode 100644 index 0000000..b106d08 --- /dev/null +++ b/src/idle-muc-channel.h @@ -0,0 +1,114 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_MUC_CHANNEL_H__ +#define __IDLE_MUC_CHANNEL_H__ + +#include <glib-object.h> + +#include "idle-handles.h" +#include "telepathy-constants.h" + +G_BEGIN_DECLS + +typedef struct _IdleMUCChannel IdleMUCChannel; +typedef struct _IdleMUCChannelClass IdleMUCChannelClass; + +struct _IdleMUCChannelClass { + GObjectClass parent_class; +}; + +struct _IdleMUCChannel { + GObject parent; +}; + +typedef enum +{ + MUC_CHANNEL_JOIN_ERROR_NONE, + MUC_CHANNEL_JOIN_ERROR_BANNED, + MUC_CHANNEL_JOIN_ERROR_INVITE_ONLY, + MUC_CHANNEL_JOIN_ERROR_FULL +} IdleMUCChannelJoinError; + +GType idle_muc_channel_get_type(void); + +/* TYPE MACROS */ +#define IDLE_TYPE_MUC_CHANNEL \ + (idle_muc_channel_get_type()) +#define IDLE_MUC_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannel)) +#define IDLE_MUC_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelClass)) +#define IDLE_IS_MUC_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_MUC_CHANNEL)) +#define IDLE_IS_MUC_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_MUC_CHANNEL)) +#define IDLE_MUC_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), IDLE_TYPE_MUC_CHANNEL, IdleMUCChannelClass)) + +gboolean idle_muc_channel_acknowledge_pending_messages (IdleMUCChannel *obj, const GArray *ids, GError **error); +gboolean idle_muc_channel_add_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error); +gboolean idle_muc_channel_close (IdleMUCChannel *obj, GError **error); +gboolean idle_muc_channel_get_all_members (IdleMUCChannel *obj, GArray ** ret, GArray ** ret1, GArray ** ret2, GError **error); +gboolean idle_muc_channel_get_channel_type (IdleMUCChannel *obj, gchar ** ret, GError **error); +gboolean idle_muc_channel_get_group_flags (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_get_handle (IdleMUCChannel *obj, guint* ret, guint* ret1, GError **error); +gboolean idle_muc_channel_get_handle_owners (IdleMUCChannel *obj, const GArray * handles, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_interfaces (IdleMUCChannel *obj, gchar *** ret, GError **error); +gboolean idle_muc_channel_get_local_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_message_types (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_password_flags (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_get_properties (IdleMUCChannel *obj, const GArray * properties, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_get_remote_pending_members (IdleMUCChannel *obj, GArray ** ret, GError **error); +gboolean idle_muc_channel_get_self_handle (IdleMUCChannel *obj, guint* ret, GError **error); +gboolean idle_muc_channel_list_pending_messages (IdleMUCChannel *obj, gboolean clear, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_list_properties (IdleMUCChannel *obj, GPtrArray ** ret, GError **error); +gboolean idle_muc_channel_provide_password (IdleMUCChannel *obj, const gchar * password, DBusGMethodInvocation *ctx); +gboolean idle_muc_channel_remove_members (IdleMUCChannel *obj, const GArray * contacts, const gchar * message, GError **error); +gboolean idle_muc_channel_send (IdleMUCChannel *obj, guint type, const gchar * text, GError **error); +gboolean idle_muc_channel_set_properties (IdleMUCChannel *obj, const GPtrArray * properties, GError **error); + +void _idle_muc_channel_join_attempt(IdleMUCChannel *chan); + +gboolean _idle_muc_channel_receive(IdleMUCChannel *chan, TpChannelTextMessageType type, IdleHandle sender, const gchar *msg); +void _idle_muc_channel_rename(IdleMUCChannel *chan, guint old, guint _new); + +void _idle_muc_channel_join(IdleMUCChannel *chan, const gchar *nick); +void _idle_muc_channel_part(IdleMUCChannel *chan, const gchar *nick); +void _idle_muc_channel_kick(IdleMUCChannel *chan, const gchar *nick, const gchar *kicker, TpChannelGroupChangeReason reason); +void _idle_muc_channel_handle_quit(IdleMUCChannel *chan, IdleHandle handle, gboolean suppress, IdleHandle actor, TpChannelGroupChangeReason reason); +void _idle_muc_channel_invited(IdleMUCChannel *chan, IdleHandle inviter); + +void _idle_muc_channel_names(IdleMUCChannel *chan, GArray *nicks); +void _idle_muc_channel_mode(IdleMUCChannel *chan, const gchar *params); +void _idle_muc_channel_topic(IdleMUCChannel *chan, const gchar *topic); +void _idle_muc_channel_topic_touch(IdleMUCChannel *chan, const IdleHandle handle, const guint timestamp); +void _idle_muc_channel_topic_full(IdleMUCChannel *chan, const IdleHandle handle, const guint timestamp, const gchar *topic); +void _idle_muc_channel_topic_unset(IdleMUCChannel *chan); + +void _idle_muc_channel_badchannelkey(IdleMUCChannel *chan); +void _idle_muc_channel_join_error(IdleMUCChannel *chan, IdleMUCChannelJoinError err); + +gboolean _idle_muc_channel_has_current_member(IdleMUCChannel *chan, IdleHandle handle); + +G_END_DECLS + +#endif /* #ifndef __IDLE_MUC_CHANNEL_H__*/ diff --git a/src/idle-server-connection-iface-signals-marshal.list b/src/idle-server-connection-iface-signals-marshal.list new file mode 100644 index 0000000..6cb7431 --- /dev/null +++ b/src/idle-server-connection-iface-signals-marshal.list @@ -0,0 +1 @@ +VOID:UINT,UINT diff --git a/src/idle-server-connection-iface.c b/src/idle-server-connection-iface.c new file mode 100644 index 0000000..8895b02 --- /dev/null +++ b/src/idle-server-connection-iface.c @@ -0,0 +1,98 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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-object.h> + +#include "idle-server-connection-iface.h" + +#include "idle-server-connection-iface-signals-marshal.h" + +static void idle_server_connection_iface_base_init(gpointer klass) +{ + static gboolean initialized = FALSE; + + if (!initialized) + { + initialized = TRUE; + + g_signal_new("status-changed", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + idle_server_connection_iface_marshal_VOID__UINT_UINT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + g_signal_new("received", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + } +} + +GType idle_server_connection_iface_get_type(void) +{ + static GType type = 0; + + if (type == 0) + { + static const GTypeInfo info = + { + sizeof (IdleServerConnectionIfaceClass), + idle_server_connection_iface_base_init, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL, + NULL + }; + + type = g_type_register_static(G_TYPE_INTERFACE, "IdleServerConnectionIface", &info, 0); + } + + return type; +} + +gboolean idle_server_connection_iface_connect(IdleServerConnectionIface *iface, GError **error) +{ + return IDLE_SERVER_CONNECTION_IFACE_GET_CLASS(iface)->connect(iface, error); +} + +gboolean idle_server_connection_iface_disconnect(IdleServerConnectionIface *iface, GError **error) +{ + return IDLE_SERVER_CONNECTION_IFACE_GET_CLASS(iface)->disconnect(iface, error); +} + +gboolean idle_server_connection_iface_send(IdleServerConnectionIface *iface, const gchar *cmd, GError **error) +{ + return IDLE_SERVER_CONNECTION_IFACE_GET_CLASS(iface)->send(iface, cmd, error); +} + +IdleServerConnectionState idle_server_connection_get_state(IdleServerConnectionIface *iface) +{ + return IDLE_SERVER_CONNECTION_IFACE_GET_CLASS(iface)->get_state(iface); +} + diff --git a/src/idle-server-connection-iface.h b/src/idle-server-connection-iface.h new file mode 100644 index 0000000..c55a11f --- /dev/null +++ b/src/idle-server-connection-iface.h @@ -0,0 +1,87 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_SERVER_CONNECTION_IFACE_H__ +#define __IDLE_SERVER_CONNECTION_IFACE_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define IDLE_TYPE_SERVER_CONNECTION_IFACE idle_server_connection_iface_get_type() + +#define IDLE_SERVER_CONNECTION_IFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + IDLE_TYPE_SERVER_CONNECTION_IFACE, IdleServerConnectionIface)) + +#define IDLE_SERVER_CONNECTION_IFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((obj), \ + IDLE_TYPE_SERVER_CONNECTION_IFACE, IdleServerConnectionIfaceClass)) + +#define IDLE_IS_SERVER_CONNECTION_IFACE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_SERVER_CONNECTION_IFACE)) + +#define IDLE_IS_SERVER_CONNECTION_IFACE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_SERVER_CONNECTION_IFACE)) + +#define IDLE_SERVER_CONNECTION_IFACE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE((obj), \ + IDLE_TYPE_SERVER_CONNECTION_IFACE, IdleServerConnectionIfaceClass)) + +typedef struct _IdleServerConnectionIface IdleServerConnectionIface; +typedef struct _IdleServerConnectionIfaceClass IdleServerConnectionIfaceClass; + +typedef enum +{ + SERVER_CONNECTION_STATE_NOT_CONNECTED, + SERVER_CONNECTION_STATE_CONNECTING, + SERVER_CONNECTION_STATE_CONNECTED, +} IdleServerConnectionState; + +typedef enum +{ + SERVER_CONNECTION_STATE_REASON_ERROR, + SERVER_CONNECTION_STATE_REASON_REQUESTED +} IdleServerConnectionStateReason; + +struct _IdleServerConnectionIfaceClass +{ + GTypeInterface parent_class; + + gboolean (*connect) (IdleServerConnectionIface *, GError **error); + gboolean (*disconnect) (IdleServerConnectionIface *, GError **error); + + gboolean (*send) (IdleServerConnectionIface *, const gchar *cmd, GError **error); + + IdleServerConnectionState (*get_state) (IdleServerConnectionIface *); +}; + +GType idle_server_connection_iface_get_type(void); + +gboolean idle_server_connection_iface_connect(IdleServerConnectionIface *, GError **error); +gboolean idle_server_connection_iface_disconnect(IdleServerConnectionIface *, GError **error); + +gboolean idle_server_connection_iface_send(IdleServerConnectionIface *, const gchar *cmd, GError **error); + +IdleServerConnectionState idle_server_connection_iface_get_state(IdleServerConnectionIface *); + +G_END_DECLS + +#endif diff --git a/src/idle-server-connection-util.c b/src/idle-server-connection-util.c new file mode 100644 index 0000000..71b7b3c --- /dev/null +++ b/src/idle-server-connection-util.c @@ -0,0 +1,104 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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> + +#define __USE_GNU +#include <string.h> + +#include "idle-server-connection-util.h" + +void msg_split(gchar *msg, gchar *split_buf, gsize max_len, message_cb cb, gpointer user_data) +{ + int i; + int lasti = 0; + gchar *tmp; + gboolean line_ends = FALSE; + guint len; + + g_assert(msg != NULL); + g_assert(split_buf != NULL); + g_assert(cb != NULL); + + len = strnlen(msg, max_len); + + for (i = 0; i < len; i++) + { + if ((msg[i] == '\n' || msg[i] == '\r')) + { + msg[i] = '\0'; + + if (i>lasti) + { + if ((lasti == 0) && (split_buf[0] != '\0')) + { + tmp = g_strconcat(split_buf, msg, NULL); + memset(split_buf, '\0', max_len); + } + else + { + tmp = g_strdup(msg+lasti); + } + + cb(tmp, user_data); + + g_free(tmp); + } + + lasti = i+1; + + line_ends = TRUE; + } + else + { + line_ends = FALSE; + } + } + + if (!line_ends) + { + g_strlcpy(split_buf, msg+lasti, max_len-lasti); + } + else + { + memset(split_buf, '\0', max_len); + } +} + +void idle_output_pending_msg_free(IdleOutputPendingMsg *msg) +{ + g_free(msg->message); + g_slice_free(IdleOutputPendingMsg, msg); +} + +gint pending_msg_compare(gconstpointer a, gconstpointer b, gpointer unused) +{ + const IdleOutputPendingMsg *msg1 = a, *msg2 = b; + + if (msg1->priority < msg2->priority) + { + return 1; + } + else + { + return -1; + } +} + diff --git a/src/idle-server-connection-util.h b/src/idle-server-connection-util.h new file mode 100644 index 0000000..22930b9 --- /dev/null +++ b/src/idle-server-connection-util.h @@ -0,0 +1,62 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_SERVER_CONNECTION_UTIL_H__ +#define __IDLE_SERVER_CONNECTION_UTIL_H__ + +typedef void (*message_cb)(const gchar *msg, gpointer user_data); + +void msg_split(gchar *msg, gchar *splitbuf, gsize max_len, message_cb cb, gpointer user_data); + +typedef struct _IdleOutputPendingMsg IdleOutputPendingMsg; + +struct _IdleOutputPendingMsg +{ + gchar *message; + guint priority; +}; + +#define idle_output_pending_msg_new() \ + (g_slice_new(IdleOutputPendingMsg)) +#define idle_output_pending_msg_new0() \ + (g_slice_new0(IdleOutputPendingMsg)) + +void idle_output_pending_msg_free(IdleOutputPendingMsg *msg); + +#define SERVER_CMD_MIN_PRIORITY 0 +#define SERVER_CMD_NORMAL_PRIORITY G_MAXUINT/2 +#define SERVER_CMD_MAX_PRIORITY G_MAXUINT + +gint pending_msg_compare(gconstpointer a, gconstpointer b, gpointer unused); + +#define IRC_MSG_MAXLEN 510 + +/* From RFC 2813 : + * This in essence means that the client may send one (1) message every + * two (2) seconds without being adversely affected. Services MAY also + * be subject to this mechanism. + */ + +#define MSG_QUEUE_UNLOAD_AT_A_TIME 1 +#define MSG_QUEUE_TIMEOUT 2 + +#define CONNECTION_TIMEOUT 15000 + +#endif diff --git a/src/idle-server-connection.c b/src/idle-server-connection.c new file mode 100644 index 0000000..63fbcd9 --- /dev/null +++ b/src/idle-server-connection.c @@ -0,0 +1,797 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <glib-object.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <time.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> + +#include "idle-server-connection.h" +#include "idle-server-connection-iface.h" +#include "idle-server-connection-util.h" + +#include "telepathy-errors.h" + +#include "idle-dns-resolver.h" + +static void idle_server_connection_iface_init(gpointer g_iface, gpointer iface_data); +typedef struct _IdleServerConnectionPrivate IdleServerConnectionPrivate; + +#define IDLE_SERVER_CONNECTION_GET_PRIVATE(conn) (G_TYPE_INSTANCE_GET_PRIVATE((conn), IDLE_TYPE_SERVER_CONNECTION, IdleServerConnectionPrivate)) + +G_DEFINE_TYPE_WITH_CODE(IdleServerConnection, idle_server_connection, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(IDLE_TYPE_SERVER_CONNECTION_IFACE, idle_server_connection_iface_init)); + +enum +{ + PROP_HOST = 1, + PROP_PORT +}; + +struct _AsyncConnectData +{ + guint watch_id; + + guint fd; + GIOChannel *io_chan; + + IdleDNSResult *res; + IdleDNSResult *cur; +}; + +#define async_connect_data_new() \ + (g_slice_new(struct _AsyncConnectData)) +#define async_connect_data_new0() \ + (g_slice_new0(struct _AsyncConnectData)) + +static void async_connect_data_destroy(struct _AsyncConnectData *data) +{ + if (data->watch_id) + { + g_source_remove(data->watch_id); + data->watch_id = 0; + } + + if (data->fd) + { + close(data->fd); + data->fd = 0; + } + + if (data->io_chan) + { + g_io_channel_shutdown(data->io_chan, FALSE, NULL); + g_io_channel_unref(data->io_chan); + data->io_chan = NULL; + } + + if (data->res) + { + idle_dns_result_destroy(data->res); + data->res = NULL; + } + + g_slice_free(struct _AsyncConnectData, data); +} + +struct _IdleServerConnectionPrivate +{ + gchar *host; + guint port; + + GIOChannel *io_chan; + IdleServerConnectionState state; + + guint read_watch_id; + + gboolean dispose_has_run; + + IdleDNSResolver *resolver; + struct _AsyncConnectData *connect_data; +}; + +static GObject *idle_server_connection_constructor(GType type, guint n_props, GObjectConstructParam *props); + +static void idle_server_connection_init(IdleServerConnection *conn) +{ + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + priv->host = NULL; + priv->port = 0; + + priv->io_chan = NULL; + priv->state = SERVER_CONNECTION_STATE_NOT_CONNECTED; + priv->read_watch_id = 0; + priv->connect_data = NULL; + priv->resolver = idle_dns_resolver_new(); + + priv->dispose_has_run = FALSE; +} + +static GObject *idle_server_connection_constructor(GType type, guint n_props, GObjectConstructParam *props) +{ + GObject *ret; + + ret = G_OBJECT_CLASS(idle_server_connection_parent_class)->constructor(type, n_props, props); + + return ret; +} + +static void idle_server_connection_dispose(GObject *obj) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(obj); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (priv->dispose_has_run) + { + return; + } + + g_debug("%s: dispose called", G_STRFUNC); + priv->dispose_has_run = TRUE; + + if (priv->state == SERVER_CONNECTION_STATE_CONNECTED) + { + GError *error = NULL; + g_warning("%s: connection was open when the object was deleted, it'll probably crash now...", G_STRFUNC); + if (!idle_server_connection_iface_disconnect(IDLE_SERVER_CONNECTION_IFACE(obj), &error)) + { + g_error_free(error); + } + } + + if (priv->connect_data != NULL) + { + async_connect_data_destroy(priv->connect_data); + priv->connect_data = NULL; + } + + if (priv->resolver != NULL) + { + idle_dns_resolver_destroy(priv->resolver); + priv->resolver = NULL; + } +} + +static void idle_server_connection_finalize(GObject *obj) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(obj); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + g_free(priv->host); +} + +static void idle_server_connection_get_property(GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(obj); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + switch (prop_id) + { + case PROP_HOST: + { + g_value_set_string(value, priv->host); + } + break; + case PROP_PORT: + { + g_value_set_uint(value, priv->port); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void idle_server_connection_set_property(GObject *obj, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(obj); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + switch (prop_id) + { + case PROP_HOST: + { + g_free(priv->host); + priv->host = g_value_dup_string(value); + } + break; + case PROP_PORT: + { + priv->port = g_value_get_uint(value); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void idle_server_connection_class_init(IdleServerConnectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GParamSpec *pspec; + + g_type_class_add_private(klass, sizeof(IdleServerConnectionPrivate)); + + object_class->constructor = idle_server_connection_constructor; + object_class->dispose = idle_server_connection_dispose; + object_class->finalize = idle_server_connection_finalize; + + object_class->get_property = idle_server_connection_get_property; + object_class->set_property = idle_server_connection_set_property; + + pspec = g_param_spec_string("host", "Remote host", + "Hostname of the remote service to connect to.", + NULL, + G_PARAM_READABLE| + G_PARAM_WRITABLE| + G_PARAM_STATIC_NICK| + G_PARAM_STATIC_BLURB); + + g_object_class_install_property(object_class, PROP_HOST, pspec); + + pspec = g_param_spec_uint("port", "Remote port", + "Port number of the remote service to connect to.", + 0, 0xffff, 0, + G_PARAM_READABLE| + G_PARAM_WRITABLE| + G_PARAM_STATIC_NICK| + G_PARAM_STATIC_BLURB); + + g_object_class_install_property(object_class, PROP_PORT, pspec); +} + +static void change_state(IdleServerConnection *conn, IdleServerConnectionState state, guint reason) +{ + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (state == priv->state) + { + return; + } + + g_debug("%s: emitting status-changed, state %u, reason %u", G_STRFUNC, state, reason); + + priv->state = state; + g_signal_emit_by_name(conn, "status-changed", state, reason); +} + +static gboolean iface_disconnect_impl_full(IdleServerConnectionIface *iface, GError **error, guint reason); + +static gboolean io_err_cleanup_func(gpointer data) +{ + GError *error = NULL; + + if (!iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(data), &error, SERVER_CONNECTION_STATE_REASON_ERROR)) + { + g_debug("%s: disconnect: %s", G_STRFUNC, error->message); + g_error_free(error); + } + + return FALSE; +} + +static gboolean io_func(GIOChannel *src, GIOCondition cond, gpointer data) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(data); + gchar buf[IRC_MSG_MAXLEN+3]; + GIOStatus status; + gsize len; + GError *error = NULL; + + if (cond & (G_IO_ERR|G_IO_HUP)) + { + g_debug("%s: got G_IO_ERR|G_IO_HUP", G_STRFUNC); + g_idle_add(io_err_cleanup_func, data); + return FALSE; + } + + memset(buf, 0, IRC_MSG_MAXLEN+3); + status = g_io_channel_read_chars(src, buf, IRC_MSG_MAXLEN+2, &len, &error); + + if ((status != G_IO_STATUS_NORMAL) && (status != G_IO_STATUS_AGAIN)) + { + g_debug("%s: status: %u, error: %s", G_STRFUNC, status, (error != NULL) ? (error->message != NULL) ? error->message : "(null)" : "(null)"); + + if (error) + { + g_error_free(error); + } + + g_idle_add(io_err_cleanup_func, data); + return FALSE; + } + + g_signal_emit_by_name(conn, "received", buf); + + return TRUE; +} + +static gboolean do_connect(IdleServerConnection *conn); + +static gboolean iface_connect_impl(IdleServerConnectionIface *iface, GError **error) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (priv->state != SERVER_CONNECTION_STATE_NOT_CONNECTED) + { + g_debug("%s: already connecting or connected!", G_STRFUNC); + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "already connecting or connected!"); + return FALSE; + } + + if ((priv->host == NULL) || (priv->host[0] == '\0')) + { + g_debug("%s: host not set!", G_STRFUNC); + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "host not set!"); + return FALSE; + } + + if (priv->port == 0) + { + g_debug("%s: port not set!", G_STRFUNC); + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "port not set!"); + return FALSE; + } + + if (!do_connect(conn)) + { + g_debug("%s: do_connect failed", G_STRFUNC); + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "failed to connect"); + return FALSE; + } + + change_state(conn, SERVER_CONNECTION_STATE_CONNECTING, SERVER_CONNECTION_STATE_REASON_REQUESTED); + + return TRUE; +} + +static gboolean connect_io_func(GIOChannel *src, GIOCondition cond, gpointer data) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(data); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + int rc; + int fd = -1; + int optval; + socklen_t optlen = sizeof(optval); + GIOChannel *io_chan; + struct _AsyncConnectData *connect_data = priv->connect_data; + + IdleDNSResult *cur = connect_data->cur; + IdleDNSResult *next = cur->ai_next; + + g_assert(getsockopt(connect_data->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == 0); + + if (optval == 0) + { + int opt; + + g_debug("%s: connected!", G_STRFUNC); + + fd = connect_data->fd; + + change_state(conn, SERVER_CONNECTION_STATE_CONNECTED, SERVER_CONNECTION_STATE_REASON_REQUESTED); + + g_assert(priv->io_chan == NULL); + g_assert(priv->read_watch_id == 0); + + priv->read_watch_id = g_io_add_watch(connect_data->io_chan, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP, io_func, data); + priv->io_chan = connect_data->io_chan; + + connect_data->io_chan = NULL; + connect_data->fd = 0; + + async_connect_data_destroy(priv->connect_data); + priv->connect_data = NULL; + + opt = 1; + + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) + { + g_warning("%s: failed to set TCP_NODELAY", G_STRFUNC); + } + + opt = 1; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) < 0) + { + g_warning("%s: failed to set SO_KEEPALIVE", G_STRFUNC); + } + + return FALSE; + } + + g_debug("%s: connection failed", G_STRFUNC); + + if (next == NULL) + { + g_debug("%s: and this was the last address we can try, tough luck.", G_STRFUNC); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + + return FALSE; + } + + if ((next->ai_family == cur->ai_family) && + (next->ai_socktype == cur->ai_socktype) && + (next->ai_protocol == cur->ai_protocol)) + { + int i; + + g_debug("%s: re-using existing socket for trying again", G_STRFUNC); + + connect(connect_data->fd, next->ai_addr, next->ai_addrlen); + + for (i=0; i<5 && errno == ECONNABORTED; i++) + { + g_debug("%s: got ECONNABORTED for (%i+1)th time", G_STRFUNC, i); + connect(connect_data->fd, next->ai_addr, next->ai_addrlen); + } + + if (errno != EINPROGRESS) + { + g_debug("%s: connect() failed: %s", G_STRFUNC, g_strerror(errno)); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return FALSE; + } + + connect_data->cur = next; + + return TRUE; + } + + g_debug("%s: we'll have to create a new socket since the address family/socket type/protocol is different", G_STRFUNC); + + for (cur = next; cur != NULL; cur = cur->ai_next) + { + fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); + + if (fd == -1) + { + if ((errno == EINVAL) || (errno == EAFNOSUPPORT)) + { + continue; + } + + g_debug("%s: socket() failed: %s", G_STRFUNC, g_strerror(errno)); + return FALSE; + } + else + { + break; + } + } + + if (fd == -1) + { + g_debug("%s: could not socket(): %s", G_STRFUNC, g_strerror(errno)); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return FALSE; + } + + io_chan = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(io_chan, NULL, NULL); + g_io_channel_set_buffered(io_chan, FALSE); + + rc = fcntl(fd, F_SETFL, O_NONBLOCK); + + if (rc != 0) + { + g_debug("%s: failed to set socket to non-blocking mode: %s", G_STRFUNC, g_strerror(errno)); + g_io_channel_shutdown(io_chan, FALSE, NULL); + g_io_channel_unref(io_chan); + close(fd); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return FALSE; + } + + rc = connect(fd, cur->ai_addr, cur->ai_addrlen); + + g_assert(rc == -1); + + if (errno != EINPROGRESS) + { + g_debug("%s: initial connect() failed: %s", G_STRFUNC, g_strerror(errno)); + g_io_channel_shutdown(io_chan, FALSE, NULL); + g_io_channel_unref(io_chan); + close(fd); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return FALSE; + } + + g_io_channel_shutdown(connect_data->io_chan, FALSE, NULL); + g_io_channel_unref(connect_data->io_chan); + connect_data->io_chan = io_chan; + + close(connect_data->fd); + connect_data->fd = fd; + + connect_data->watch_id = g_io_add_watch(io_chan, G_IO_OUT|G_IO_ERR, connect_io_func, conn); + + return FALSE; +} + +static void dns_result_callback(guint unused, IdleDNSResult *results, gpointer user_data) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(user_data); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + IdleDNSResult *cur; + int fd = -1; + int rc = -1; + GIOChannel *io_chan; + + if (!results) + { + g_debug("%s: no DNS results received", G_STRFUNC); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return; + } + + for (cur = results; cur != NULL; cur = cur->ai_next) + { + fd = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); + + if (fd == -1) + { + if ((errno == EINVAL) || (errno = EAFNOSUPPORT)) + { + continue; + } + + g_debug("%s: socket() failed: %s", G_STRFUNC, g_strerror(errno)); + return; + } + else + { + break; + } + } + + if (fd == -1) + { + g_debug("%s: failed: %s", G_STRFUNC, g_strerror(errno)); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return; + } + + rc = fcntl(fd, F_SETFL, O_NONBLOCK); + + if (rc != 0) + { + g_debug("%s: failed to set socket to non-blocking mode: %s", G_STRFUNC, g_strerror(errno)); + close(fd); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return; + } + + rc = connect(fd, cur->ai_addr, cur->ai_addrlen); + + g_assert(rc == -1); + + if (errno != EINPROGRESS) + { + g_debug("%s: initial connect() failed: %s", G_STRFUNC, g_strerror(errno)); + close(fd); + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + return; + } + + if (priv->connect_data != NULL) + { + async_connect_data_destroy(priv->connect_data); + } + + priv->connect_data = async_connect_data_new(); + + io_chan = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(io_chan, NULL, NULL); + g_io_channel_set_buffered(io_chan, FALSE); + + priv->connect_data->fd = fd; + priv->connect_data->io_chan = io_chan; + priv->connect_data->res = results; + priv->connect_data->cur = cur; + priv->connect_data->watch_id = g_io_add_watch(io_chan, G_IO_OUT|G_IO_ERR, connect_io_func, conn); +} + +static gboolean do_connect(IdleServerConnection *conn) +{ + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + idle_dns_resolver_query(priv->resolver, priv->host, priv->port, dns_result_callback, conn); + + return TRUE; +} + +static gboolean iface_disconnect_impl(IdleServerConnectionIface *iface, GError **error) +{ + return iface_disconnect_impl_full(iface, error, SERVER_CONNECTION_STATE_REASON_REQUESTED); +} + +static gboolean iface_disconnect_impl_full(IdleServerConnectionIface *iface, GError **error, guint reason) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + GError *io_error = NULL; + int fd; + GIOStatus status; + + g_assert(priv != NULL); + + if (priv->state != SERVER_CONNECTION_STATE_CONNECTED) + { + g_debug("%s: the connection was not open", G_STRFUNC); + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "the connection was not open"); + return FALSE; + } + + if (priv->read_watch_id != 0) + { + g_source_remove(priv->read_watch_id); + priv->read_watch_id = 0; + } + + if (priv->io_chan != NULL) + { + fd = g_io_channel_unix_get_fd(priv->io_chan); + status = g_io_channel_shutdown(priv->io_chan, TRUE, &io_error); + + if (status != G_IO_STATUS_NORMAL && io_error) + { + g_debug("%s: g_io_channel_shutdown failed: %s", G_STRFUNC, io_error->message); + g_error_free(io_error); + } + + if (close(fd)) + { + g_debug("%s: unable to close fd: %s", G_STRFUNC, g_strerror(errno)); + } + + g_io_channel_unref(priv->io_chan); + priv->io_chan = NULL; + } + + change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, reason); + + return TRUE; +} + +static gboolean iface_send_impl(IdleServerConnectionIface *iface, const gchar *cmd, GError **error) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + GIOStatus status; + gsize written; + GError *local_error = NULL; + + if (priv->state != SERVER_CONNECTION_STATE_CONNECTED) + { + g_debug("%s: connection was not open!", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "connection was not open!"); + + return FALSE; + } + + status = g_io_channel_write_chars(priv->io_chan, + cmd, + -1, + &written, + &local_error); + + if (local_error) + { + g_debug("%s: error: %s", G_STRFUNC, local_error->message); + g_error_free(local_error); + } + + switch (status) + { + case G_IO_STATUS_ERROR: + { + g_debug("%s: got G_IO_STATUS_ERROR", G_STRFUNC); + + if (iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), &local_error, SERVER_CONNECTION_STATE_REASON_ERROR)) + { + g_error_free(local_error); + } + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "got G_IO_STATUS_ERROR"); + + return FALSE; + } + break; + case G_IO_STATUS_NORMAL: + { + g_debug("%s: sent \"%s\"", G_STRFUNC, cmd); + + return TRUE; + } + break; + case G_IO_STATUS_EOF: + { + g_debug("%s: got G_IO_STATUS_EOF", G_STRFUNC); + + if (iface_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), &local_error, SERVER_CONNECTION_STATE_REASON_ERROR)) + { + g_error_free(local_error); + } + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "got G_IO_STATUS_EOF"); + + return FALSE; + } + break; + case G_IO_STATUS_AGAIN: + { + g_debug("%s: got G_IO_STATUS_AGAIN", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "got G_IO_STATUS_AGAIN"); + + return FALSE; + } + break; + default: + { + g_assert_not_reached(); + return FALSE; + } + } +} + +static IdleServerConnectionState iface_get_state_impl(IdleServerConnectionIface *iface) +{ + IdleServerConnection *conn = IDLE_SERVER_CONNECTION(iface); + IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn); + + return priv->state; +} + +static void idle_server_connection_iface_init(gpointer g_iface, gpointer iface_data) +{ + IdleServerConnectionIfaceClass *klass = (IdleServerConnectionIfaceClass *)(g_iface); + + klass->connect = iface_connect_impl; + klass->disconnect = iface_disconnect_impl; + klass->send = iface_send_impl; + klass->get_state = iface_get_state_impl; +} diff --git a/src/idle-server-connection.h b/src/idle-server-connection.h new file mode 100644 index 0000000..b5786f7 --- /dev/null +++ b/src/idle-server-connection.h @@ -0,0 +1,63 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_SERVER_CONNECTION_H__ +#define __IDLE_SERVER_CONNECTION_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleServerConnection IdleServerConnection; +typedef struct _IdleServerConnectionClass IdleServerConnectionClass; + +struct _IdleServerConnection +{ + GObject parent; +}; + +struct _IdleServerConnectionClass +{ + GObjectClass parent; +}; + +GType idle_server_connection_get_type(void); + +#define IDLE_TYPE_SERVER_CONNECTION \ + (idle_server_connection_get_type()) + +#define IDLE_SERVER_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_SERVER_CONNECTION, IdleServerConnection)) + +#define IDLE_SERVER_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_SERVER_CONNECTION, IdleServerConnection)) + +#define IDLE_IS_SERVER_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_SERVER_CONNECTION)) + +#define IDLE_IS_SERVER_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_SERVER_CONNECTION)) + +#define IDLE_SERVER_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), IDLE_TYPE_SERVER_CONNECTION, IdleServerConnectionClass)) + +G_END_DECLS + +#endif diff --git a/src/idle-ssl-server-connection.c b/src/idle-ssl-server-connection.c new file mode 100644 index 0000000..a368cc0 --- /dev/null +++ b/src/idle-ssl-server-connection.c @@ -0,0 +1,766 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <glib-object.h> + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <time.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> + +#include <openssl/ssl.h> +#include <openssl/err.h> + +#include "idle-ssl-server-connection.h" +#include "idle-server-connection-iface.h" + +#include "idle-server-connection-util.h" + +#include "telepathy-errors.h" + +#include "idle-dns-resolver.h" + +/* From RFC 2813 : + * This in essence means that the client may send one (1) message every + * two (2) seconds without being adversely affected. Services MAY also + * be subject to this mechanism. + */ + +#define SEND_QUEUE_UNLOAD_AT_A_TIME 1 +#define SEND_QUEUE_UNLOAD_DELAY 2 + +static void idle_ssl_server_connection_iface_init(gpointer g_iface, gpointer iface_data); +typedef struct _IdleSSLServerConnectionPrivate IdleSSLServerConnectionPrivate; + +#define IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn) \ + (G_TYPE_INSTANCE_GET_PRIVATE((conn), IDLE_TYPE_SSL_SERVER_CONNECTION, \ + IdleSSLServerConnectionPrivate)) + +G_DEFINE_TYPE_WITH_CODE(IdleSSLServerConnection, idle_ssl_server_connection, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(IDLE_TYPE_SERVER_CONNECTION_IFACE, idle_ssl_server_connection_iface_init)); + +enum +{ + PROP_HOST = 1, + PROP_PORT, +}; + +typedef void (*async_connecting_finished_cb)(IdleSSLServerConnection *conn, gboolean success); + +typedef struct _AsyncConnectData AsyncConnectData; +struct _AsyncConnectData +{ + IdleSSLServerConnection *conn; + + guint watch_id; + GIOChannel *io_chan; + guint fd; + + IdleDNSResult *res; + IdleDNSResult *cur; + + async_connecting_finished_cb finished_cb; +}; + +#define async_connect_data_new() \ + (g_slice_new(AsyncConnectData)) +#define async_connect_data_new0() \ + (g_slice_new0(AsyncConnectData)) + +static void async_connect_data_destroy(AsyncConnectData *data) +{ + if (data->watch_id) + { + g_source_remove(data->watch_id); + data->watch_id = 0; + } + + if (data->io_chan) + { + g_io_channel_shutdown(data->io_chan, FALSE, NULL); + g_io_channel_unref(data->io_chan); + data->io_chan = NULL; + } + + if (data->fd) + { + close(data->fd); + data->fd = 0; + } + + if (data->res) + { + idle_dns_result_destroy(data->res); + data->res = NULL; + data->cur = NULL; + } + + g_slice_free(AsyncConnectData, data); +} + +struct _IdleSSLServerConnectionPrivate +{ + gchar *host; + guint port; + + GIOChannel *io_chan; + int fd; + SSL *ssl; + BIO *bio; + + guint read_watch_id; + guint last_message_sent; + + IdleDNSResolver *resolver; + AsyncConnectData *connect_data; + + IdleServerConnectionState state; + + gboolean dispose_has_run; +}; + +static GObject *idle_ssl_server_connection_constructor(GType type, guint n_props, GObjectConstructParam *props); + +static void idle_ssl_server_connection_init(IdleSSLServerConnection *conn) +{ + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + priv->host = NULL; + priv->port = 0; + + priv->io_chan = NULL; + priv->fd = 0; + priv->ssl = NULL; + priv->bio = NULL; + + priv->read_watch_id = 0; + priv->last_message_sent = 0; + + priv->resolver = idle_dns_resolver_new(); + priv->connect_data = NULL; + + priv->state = SERVER_CONNECTION_STATE_NOT_CONNECTED; + + priv->dispose_has_run = FALSE; +} + +static GObject *idle_ssl_server_connection_constructor(GType type, guint n_props, GObjectConstructParam *props) +{ + GObject *ret; + + ret = G_OBJECT_CLASS(idle_ssl_server_connection_parent_class)->constructor(type, n_props, props); + + return ret; +} + +static void idle_ssl_server_connection_dispose(GObject *obj) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(obj); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (priv->dispose_has_run) + { + return; + } + + g_debug("%s: dispose called", G_STRFUNC); + priv->dispose_has_run = TRUE; + + if (priv->state == SERVER_CONNECTION_STATE_CONNECTED) + { + GError *error = NULL; + g_warning("%s: connection was open when the object was deleted, it'll probably crash now..", G_STRFUNC); + + if (!idle_server_connection_iface_disconnect(IDLE_SERVER_CONNECTION_IFACE(obj), &error)) + { + g_error_free(error); + } + } + + if (priv->ssl) + { + SSL_free(priv->ssl); + priv->ssl = NULL; + } + + if (priv->bio) + { + BIO_free(priv->bio); + priv->bio = NULL; + } + + if (priv->read_watch_id) + { + g_source_remove(priv->read_watch_id); + priv->read_watch_id = 0; + } +} + +static void idle_ssl_server_connection_finalize(GObject *obj) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(obj); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + g_free(priv->host); + + idle_dns_resolver_destroy(priv->resolver); + + if (priv->connect_data) + { + async_connect_data_destroy(priv->connect_data); + priv->connect_data = NULL; + } +} + +static void idle_ssl_server_connection_get_property(GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(obj); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + switch (prop_id) + { + case PROP_HOST: + { + g_value_set_string(value, priv->host); + } + break; + case PROP_PORT: + { + g_value_set_uint(value, priv->port); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void idle_ssl_server_connection_set_property(GObject *obj, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(obj); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + switch (prop_id) + { + case PROP_HOST: + { + g_free(priv->host); + priv->host = g_value_dup_string(value); + } + break; + case PROP_PORT: + { + priv->port = g_value_get_uint(value); + } + break; + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); + } + break; + } +} + +static void idle_ssl_server_connection_class_init(IdleSSLServerConnectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GParamSpec *pspec; + + g_type_class_add_private(klass, sizeof(IdleSSLServerConnectionPrivate)); + + object_class->constructor = idle_ssl_server_connection_constructor; + object_class->dispose = idle_ssl_server_connection_dispose; + object_class->finalize = idle_ssl_server_connection_finalize; + + object_class->get_property = idle_ssl_server_connection_get_property; + object_class->set_property = idle_ssl_server_connection_set_property; + + pspec = g_param_spec_string("host", "Remote host", + "Hostname of the remote service to connect to.", + NULL, + G_PARAM_READABLE| + G_PARAM_WRITABLE| + G_PARAM_STATIC_NICK| + G_PARAM_STATIC_BLURB); + + g_object_class_install_property(object_class, PROP_HOST, pspec); + + pspec = g_param_spec_uint("port", "Remote port", + "Port number of the remote service to connect to.", + 0, 0xffff, 0, + G_PARAM_READABLE| + G_PARAM_WRITABLE| + G_PARAM_STATIC_NICK| + G_PARAM_STATIC_BLURB); + + g_object_class_install_property(object_class, PROP_PORT, pspec); +} + +static void ssl_conn_change_state(IdleSSLServerConnection *conn, guint state, guint reason) +{ + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (state == priv->state) + { + return; + } + + priv->state = state; + + g_signal_emit_by_name(conn, "status-changed", state, reason); +} + +static SSL_CTX *ssl_conn_get_ctx() +{ + static SSL_CTX *ctx = NULL; + + if (!ctx) + { + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(SSLv23_client_method()); + + if (!ctx) + { + g_debug("%s: OpenSSL initialization failed!", G_STRFUNC); + } + } + + return ctx; +} + +static gboolean iface_ssl_disconnect_impl_full(IdleServerConnectionIface *iface, guint reason, GError **error); + +static gboolean ssl_io_err_cleanup_func(gpointer user_data) +{ + IdleServerConnectionIface *iface = IDLE_SERVER_CONNECTION_IFACE(user_data); + GError *error; + + if (!iface_ssl_disconnect_impl_full(iface, SERVER_CONNECTION_STATE_REASON_ERROR, &error)) + { + g_debug("%s: disconnect: %s", G_STRFUNC, error->message); + g_error_free(error); + } + + return FALSE; +} + +static gboolean ssl_io_func(GIOChannel *src, GIOCondition cond, gpointer data) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(data); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(data); + gchar buf[IRC_MSG_MAXLEN+3]; + int err; + + if ((cond == G_IO_ERR) || (cond == G_IO_HUP)) + { + g_debug("%s: got G_IO_ERR || G_IO_HUP", G_STRFUNC); + ssl_conn_change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + + priv->read_watch_id = 0; + return FALSE; + } + + memset(buf, 0, IRC_MSG_MAXLEN+3); + + do + { + err = SSL_read(priv->ssl, buf, IRC_MSG_MAXLEN+2); + } while ((err <= 0) && (SSL_get_error(priv->ssl, err) == SSL_ERROR_WANT_READ)); + + if (err <= 0) + { + g_debug("%s: SSL_read failed with error %i", G_STRFUNC, SSL_get_error(priv->ssl, err)); + + g_idle_add(ssl_io_err_cleanup_func, conn); + + priv->read_watch_id = 0; + return FALSE; + } + + g_signal_emit_by_name(conn, "received", buf); + + return TRUE; +} + +static void ssl_async_connecting_finished_cb(IdleSSLServerConnection *conn, gboolean success) +{ + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + SSL_CTX *ctx; + X509 *cert; + int status; + int opt; + + if (!success) + { + return ssl_conn_change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR); + } + + priv->fd = priv->connect_data->fd; + priv->connect_data->fd = 0; + + priv->io_chan = priv->connect_data->io_chan; + priv->connect_data->io_chan = NULL; + + if (priv->connect_data->watch_id) + { + g_source_remove(priv->connect_data->watch_id); + priv->connect_data->watch_id = 0; + } + + priv->read_watch_id = g_io_add_watch(priv->io_chan, G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP, ssl_io_func, conn); + + if (fcntl(priv->fd, F_SETFL, 0)) + { + g_debug("%s: failed to set socket back to blocking mode", G_STRFUNC); + } + + opt = 1; + setsockopt(priv->fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + opt = 1; + setsockopt(priv->fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + ctx = ssl_conn_get_ctx(); + + if (!ctx) + { + g_debug("%s: failed to get SSL context object", G_STRFUNC); + return ssl_async_connecting_finished_cb(conn, FALSE); + } + + priv->ssl = SSL_new(ctx); + + if (!priv->ssl) + { + g_debug("%s: failed to create SSL object", G_STRFUNC); + return ssl_async_connecting_finished_cb(conn, FALSE); + } + + status = SSL_set_fd(priv->ssl, priv->fd); + + if (!status) + { + g_debug("%s: failed to set SSL socket", G_STRFUNC); + return ssl_async_connecting_finished_cb(conn, FALSE); + } + + status = SSL_connect(priv->ssl); + + if (status <= 0) + { + g_debug("%s: SSL_connect failed with status %i (error %i)", G_STRFUNC, status, SSL_get_error(priv->ssl, status)); + return ssl_async_connecting_finished_cb(conn, FALSE); + } + + cert = SSL_get_peer_certificate(priv->ssl); + + if (!cert) + { + g_debug("%s: failed to get SSL peer certificate", G_STRFUNC); + return ssl_async_connecting_finished_cb(conn, FALSE); + } + + /* TODO sometime in the future implement certificate verification */ + + X509_free(cert); + + ssl_conn_change_state(conn, SERVER_CONNECTION_STATE_CONNECTED, SERVER_CONNECTION_STATE_REASON_REQUESTED); +} + +static void ssl_do_connect(AsyncConnectData *data); + +static gboolean ssl_connect_io_func(GIOChannel *src, GIOCondition cond, gpointer user_data) +{ + AsyncConnectData *data = (AsyncConnectData *)(user_data); + int optval; + socklen_t optlen = sizeof(optval); + + g_assert(getsockopt(data->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == 0); + + if (optval == 0) + { + data->finished_cb(data->conn, TRUE); + } + else + { + g_source_remove(data->watch_id); + data->watch_id = 0; + + g_io_channel_shutdown(data->io_chan, FALSE, NULL); + data->io_chan = NULL; + + close(data->fd); + data->fd = 0; + + ssl_do_connect(data); + } + + return FALSE; +} + +static void ssl_do_connect(AsyncConnectData *data) +{ + int fd = -1, rc = -1; + GIOChannel *io_chan; + + for (; data->cur != NULL; data->cur = data->cur->ai_next) + { + fd = socket(data->cur->ai_family, data->cur->ai_socktype, data->cur->ai_protocol); + + if (fd == -1) + { + g_debug("%s: socket() failed: %s", G_STRFUNC, g_strerror(errno)); + } + else + { + break; + } + } + + if (fd == -1) + { + g_debug("%s: failed: %s", G_STRFUNC, g_strerror(errno)); + return data->finished_cb(data->conn, FALSE); + } + + rc = fcntl(fd, F_SETFL, O_NONBLOCK); + + if (rc != 0) + { + g_debug("%s: failed to set socket to non-blocking mode: %s", G_STRFUNC, g_strerror(errno)); + close(fd); + return data->finished_cb(data->conn, FALSE); + } + + rc = connect(fd, data->cur->ai_addr, data->cur->ai_addrlen); + + g_assert(rc == -1); + + if (errno != EINPROGRESS) + { + g_debug("%s: connect() failed: %s", G_STRFUNC, g_strerror(errno)); + close(fd); + return data->finished_cb(data->conn, FALSE); + } + + io_chan = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(io_chan, NULL, NULL); + g_io_channel_set_buffered(io_chan, FALSE); + + data->fd = fd; + data->io_chan = io_chan; + data->watch_id = g_io_add_watch(io_chan, G_IO_OUT|G_IO_ERR, ssl_connect_io_func, data); +} + +static void ssl_dns_result_cb(guint unused, IdleDNSResult *results, gpointer user_data) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(user_data); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + AsyncConnectData *data; + + if (priv->connect_data) + { + async_connect_data_destroy(priv->connect_data); + } + + priv->connect_data = data = async_connect_data_new0(); + + data->conn = conn; + data->res = results; + data->cur = results; + data->finished_cb = ssl_async_connecting_finished_cb; + + return ssl_do_connect(data); +} + +static gboolean iface_ssl_connect_impl(IdleServerConnectionIface *iface, GError **error) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(iface); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (priv->state != SERVER_CONNECTION_STATE_NOT_CONNECTED) + { + g_debug("%s: connection was not in state NOT_CONNECTED", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "connection was in state NOT_CONNECTED"); + + return FALSE; + } + + if (!priv->host && !priv->host[0]) + { + g_debug("%s: no hostname provided", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "no hostname provided"); + + return FALSE; + } + + if (!priv->port) + { + g_debug("%s: no port provided", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "no port provided"); + + return FALSE; + } + + idle_dns_resolver_query(priv->resolver, priv->host, priv->port, ssl_dns_result_cb, conn); + + ssl_conn_change_state(conn, SERVER_CONNECTION_STATE_CONNECTING, SERVER_CONNECTION_STATE_REASON_REQUESTED); + + return TRUE; +} + +static gboolean iface_ssl_disconnect_impl_full(IdleServerConnectionIface *iface, guint reason, GError **error) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(iface); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + if (priv->state == SERVER_CONNECTION_STATE_NOT_CONNECTED) + { + g_debug("%s: not connected", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "not connected"); + + return FALSE; + } + + if (priv->read_watch_id) + { + g_source_remove(priv->read_watch_id); + priv->read_watch_id = 0; + } + + if (priv->io_chan) + { + GError *io_error = NULL; + + g_io_channel_shutdown(priv->io_chan, FALSE, &io_error); + + if (io_error) + { + g_debug("%s: g_io_channel_shutdown failed: %s", G_STRFUNC, io_error->message); + + g_error_free(io_error); + } + + g_io_channel_unref(priv->io_chan); + + priv->io_chan = NULL; + } + + if (priv->fd) + { + close(priv->fd); + priv->fd = 0; + } + + ssl_conn_change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, reason); + + return TRUE; +} + +static gboolean iface_ssl_disconnect_impl(IdleServerConnectionIface *iface, GError **error) +{ + return iface_ssl_disconnect_impl_full(iface, SERVER_CONNECTION_STATE_REASON_REQUESTED, error); +} + +static gboolean iface_ssl_send_impl(IdleServerConnectionIface *iface, const gchar *cmd, GError **error) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(iface); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + gsize len; + int rc; + + g_assert(cmd != NULL); + + if (priv->state != SERVER_CONNECTION_STATE_CONNECTED) + { + g_debug("%s: connection was not in state CONNECTED", G_STRFUNC); + + *error = g_error_new(TELEPATHY_ERRORS, NotAvailable, "connection was not in state CONNECTED"); + + return FALSE; + } + + len = strlen(cmd); + + if (!len) + { + return TRUE; + } + + rc = SSL_write(priv->ssl, cmd, len); + + if (rc <= 0) + { + GError *local_error; + + g_debug("%s: SSL_write failed with status %i (error %i)", G_STRFUNC, rc, SSL_get_error(priv->ssl, rc)); + + if (!iface_ssl_disconnect_impl_full(IDLE_SERVER_CONNECTION_IFACE(conn), SERVER_CONNECTION_STATE_REASON_ERROR, &local_error)) + { + g_error_free(local_error); + } + + *error = g_error_new(TELEPATHY_ERRORS, NetworkError, "SSL_write failed"); + + return FALSE; + } + + return TRUE; +} + +IdleServerConnectionState iface_ssl_get_state_impl(IdleServerConnectionIface *iface) +{ + IdleSSLServerConnection *conn = IDLE_SSL_SERVER_CONNECTION(iface); + IdleSSLServerConnectionPrivate *priv = IDLE_SSL_SERVER_CONNECTION_GET_PRIVATE(conn); + + return priv->state; +} + +static void idle_ssl_server_connection_iface_init(gpointer g_iface, gpointer iface_data) +{ + IdleServerConnectionIfaceClass *klass = (IdleServerConnectionIfaceClass *)(g_iface); + + klass->connect = iface_ssl_connect_impl; + klass->disconnect = iface_ssl_disconnect_impl; + klass->send = iface_ssl_send_impl; + klass->get_state = iface_ssl_get_state_impl; +} diff --git a/src/idle-ssl-server-connection.h b/src/idle-ssl-server-connection.h new file mode 100644 index 0000000..6cfc41d --- /dev/null +++ b/src/idle-ssl-server-connection.h @@ -0,0 +1,63 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_SSL_SERVER_CONNECTION_H__ +#define __IDLE_SSL_SERVER_CONNECTION_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _IdleSSLServerConnection IdleSSLServerConnection; +typedef struct _IdleSSLServerConnectionClass IdleSSLServerConnectionClass; + +struct _IdleSSLServerConnection +{ + GObject parent; +}; + +struct _IdleSSLServerConnectionClass +{ + GObjectClass parent; +}; + +GType idle_ssl_server_connection_get_type(void); + +#define IDLE_TYPE_SSL_SERVER_CONNECTION \ + (idle_ssl_server_connection_get_type()) + +#define IDLE_SSL_SERVER_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), IDLE_TYPE_SSL_SERVER_CONNECTION, IdleSSLServerConnection)) + +#define IDLE_SSL_SERVER_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), IDLE_TYPE_SSL_SERVER_CONNECTION, IdleSSLServerConnection)) + +#define IDLE_IS_SSL_SERVER_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), IDLE_TYPE_SSL_SERVER_CONNECTION)) + +#define IDLE_IS_SSL_SERVER_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), IDLE_TYPE_SSL_SERVER_CONNECTION)) + +#define IDLE_SSL_SERVER_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), IDLE_TYPE_SSL_SERVER_CONNECTION, IdleSSLServerConnectionClass)) + +G_END_DECLS + +#endif diff --git a/src/idle-version.h b/src/idle-version.h new file mode 100644 index 0000000..75d00e7 --- /dev/null +++ b/src/idle-version.h @@ -0,0 +1,26 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 IDLE_VERSION_H +#define IDLE_VERSION_H + +#include "config.h" + +#endif diff --git a/src/idle.c b/src/idle.c new file mode 100644 index 0000000..badabd1 --- /dev/null +++ b/src/idle.c @@ -0,0 +1,185 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 "idle.h" + +#include "idle-connection-manager.h" +#include "telepathy-errors.h" +#include "telepathy-errors-enumtypes.h" + +#include <stdlib.h> +#include <stdio.h> +#include <execinfo.h> + +GSource *timeout = NULL; +GMainLoop *mainloop = NULL; +IdleConnectionManager *manager = NULL; +gboolean connections_exist = FALSE; +guint timeout_id; + +#define DIE_TIME 5000 + +static gboolean +kill_connection_manager (gpointer data) +{ + if (!g_getenv ("IDLE_PERSIST") && !connections_exist) + { + g_debug("no connections, and timed out"); + g_object_unref (manager); + g_main_loop_quit (mainloop); + } + + return FALSE; +} + +static void +new_connection (IdleConnectionManager *conn, gchar *bus_name, + gchar *object_path, gchar *proto) +{ + g_debug("%s called with %s, %s, %s", G_STRFUNC, bus_name, object_path, proto); + connections_exist = TRUE; + g_source_remove (timeout_id); +} + +static void +no_more_connections (IdleConnectionManager *conn) +{ + if (g_main_context_find_source_by_id (g_main_loop_get_context (mainloop), + timeout_id)) + { + g_source_remove (timeout_id); + } + connections_exist = FALSE; + timeout_id = g_timeout_add(DIE_TIME, kill_connection_manager, NULL); +} + +#if 0 +static void gabble_critical_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + void *array[10]; + size_t size; + char **strings; + size_t i; + + fprintf (stderr, "%s: %s::%s, level %d\n", G_STRFUNC, log_domain, message, log_level); + + fprintf(stderr, "\n########## Backtrace ##########\n"); + size = backtrace (array, 10); + strings = backtrace_symbols (array, size); + + fprintf (stderr, "Obtained %zd stack frames.\n", size); + + for (i = 0; i < size; i++) + fprintf (stderr, "%s\n", strings[i]); + + free (strings); + abort(); +} + +static void signal_handler_helper(int signum) +{ + void *array[10]; + size_t size; + char **strings; + size_t i; + + fprintf (stderr, "%s: %d", G_STRFUNC, signum); + + fprintf(stderr, "\n########## Backtrace ##########\n"); + size = backtrace (array, 10); + strings = backtrace_symbols (array, size); + + fprintf (stderr, "Obtained %zd stack frames.\n", size); + + for (i = 0; i < size; i++) + fprintf (stderr, "%s\n", strings[i]); + + free (strings); + + exit(1); +} +#endif + +#include <signal.h> + +int main(int argc, char **argv) { + + g_type_init(); +/* + {*/ + /*GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask);*/ + + /* useful backtrace handler from gabble */ + /* + g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL| G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, gabble_critical_handler, NULL); + g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, gabble_critical_handler, NULL); + g_log_set_handler (NULL, G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL + | G_LOG_FLAG_RECURSION, gabble_critical_handler, NULL); + } +*/ + /* + signal(SIGHUP, signal_handler_helper); + signal(SIGSEGV, signal_handler_helper); + signal(SIGTERM, signal_handler_helper); + signal(SIGPIPE, signal_handler_helper); + signal(SIGABRT, signal_handler_helper); + signal(SIGINT, signal_handler_helper); +*/ + + g_set_prgname("telepathy-idle"); +#if 0 + if (!g_thread_supported()) + { + g_thread_init(NULL); + } +#endif + + mainloop = g_main_loop_new (NULL, FALSE); + + dbus_g_error_domain_register (TELEPATHY_ERRORS, + "org.freedesktop.Telepathy.Error", TELEPATHY_TYPE_ERRORS); + + manager = g_object_new (IDLE_TYPE_CONNECTION_MANAGER, NULL); + + g_signal_connect (manager, "new-connection", + (GCallback) new_connection, NULL); + + g_signal_connect (manager, "no-more-connections", + (GCallback) no_more_connections, NULL); + + _idle_connection_manager_register (manager); + + g_debug ("started"); + + timeout_id = g_timeout_add(DIE_TIME, kill_connection_manager, NULL); + + g_main_loop_run (mainloop); + + return 0; +} diff --git a/src/idle.h b/src/idle.h new file mode 100644 index 0000000..45e5d99 --- /dev/null +++ b/src/idle.h @@ -0,0 +1,30 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __IDLE_H__ +#define __IDLE_H__ + +#include <dbus/dbus-glib.h> + +G_BEGIN_DECLS + +G_END_DECLS + +#endif /* __IDLE_H__ */ diff --git a/src/telepathy-constants.h b/src/telepathy-constants.h new file mode 100644 index 0000000..db272de --- /dev/null +++ b/src/telepathy-constants.h @@ -0,0 +1,117 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __TELEPATHY_CONSTANTS_H__ +#define __TELEPATHY_CONSTANTS_H__ + +#include <glib.h> +G_BEGIN_DECLS + +typedef enum { +TP_CONN_MGR_PARAM_FLAG_REQUIRED = 1, +TP_CONN_MGR_PARAM_FLAG_REGISTER = 2, +TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT = 4 +} TpConnectionManagerParamFlag; + +typedef enum { +TP_HANDLE_TYPE_NONE = 0, +TP_HANDLE_TYPE_CONTACT = 1, +TP_HANDLE_TYPE_ROOM = 2, +TP_HANDLE_TYPE_LIST = 3 +} TpHandleType; + +typedef enum { +TP_CONN_PRESENCE_TYPE_UNSET = 0, +TP_CONN_PRESENCE_TYPE_OFFLINE = 1, +TP_CONN_PRESENCE_TYPE_AVAILABLE = 2, +TP_CONN_PRESENCE_TYPE_AWAY = 3, +TP_CONN_PRESENCE_TYPE_EXTENDED_AWAY = 4, +TP_CONN_PRESENCE_TYPE_HIDDEN = 5 +} TpConnectionPresenceType; + +typedef enum { +TP_CONN_STATUS_CONNECTED = 0, +TP_CONN_STATUS_CONNECTING = 1, +TP_CONN_STATUS_DISCONNECTED = 2 +} TpConnectionStatus; + +typedef enum { +TP_CONN_STATUS_REASON_NONE_SPECIFIED = 0, +TP_CONN_STATUS_REASON_REQUESTED = 1, +TP_CONN_STATUS_REASON_NETWORK_ERROR = 2, +TP_CONN_STATUS_REASON_AUTHENTICATION_FAILED = 3, +TP_CONN_STATUS_REASON_ENCRYPTION_ERROR = 4, +TP_CONN_STATUS_REASON_NAME_IN_USE = 5 +} TpConnectionStatusReason; + +typedef enum { +TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL = 0, +TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION = 1, +TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE = 2 +} TpChannelTextMessageType; + +typedef enum { +TP_CHANNEL_GROUP_FLAG_CAN_ADD = 1, +TP_CHANNEL_GROUP_FLAG_CAN_REMOVE = 2, +TP_CHANNEL_GROUP_FLAG_CAN_RESCIND = 4, +TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD = 8, +TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE = 16, +TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT = 32, +TP_CHANNEL_GROUP_FLAG_MESSAGE_REJECT = 64, +TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND = 128 +} TpChannelGroupFlags; + +typedef enum { +TP_CHANNEL_HOLD_STATE_NONE = 0, +TP_CHANNEL_HOLD_STATE_SEND_ONLY = 1, +TP_CHANNEL_HOLD_STATE_RECV_ONLY = 2, +TP_CHANNEL_HOLD_STATE_BOTH = 3 +} TpChannelHoldState; + +typedef enum { +TP_CHANNEL_PASSWORD_FLAG_PROVIDE = 8 +} TpChannelPasswordFlags; + +typedef enum { + TP_PROPERTY_FLAG_READ = 1, + TP_PROPERTY_FLAG_WRITE = 2 +} TpPropertyFlags; + +typedef enum { + TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN = 0, + TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE = 1, + TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT = 2, + TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED = 3, + TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG = 4 +} TpChannelTextSenderror; + +typedef enum { + TP_CHANNEL_GROUP_CHANGE_REASON_NONE = 0, + TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE = 1, + TP_CHANNEL_GROUP_CHANGE_REASON_KICKED = 2, + TP_CHANNEL_GROUP_CHANGE_REASON_BUSY = 3, + TP_CHANNEL_GROUP_CHANGE_REASON_INVITED = 4 +} +TpChannelGroupChangeReason; + +G_END_DECLS + + +#endif diff --git a/src/telepathy-errors.c b/src/telepathy-errors.c new file mode 100644 index 0000000..9f9b80c --- /dev/null +++ b/src/telepathy-errors.c @@ -0,0 +1,31 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 "telepathy-errors.h" + +GQuark +telepathy_errors_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("telepathy_errors"); + return quark; +} diff --git a/src/telepathy-errors.h b/src/telepathy-errors.h new file mode 100644 index 0000000..ede0742 --- /dev/null +++ b/src/telepathy-errors.h @@ -0,0 +1,60 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __TELEPATHY_ERRORS_H__ +#define __TELEPATHY_ERRORS_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef enum +{ + ChannelBanned, + ChannelFull, + ChannelInviteOnly, + InvalidHandle, /** The contact name specified is unknown on this channel + * or connection. + */ + Disconnected, /** The connection is not currently connected and cannot be + * used. + */ + InvalidArgument, /** Raised when one of the provided arguments is invalid. + */ + NetworkError, /** Raised when there is an error reading from or writing + * to the network. + */ + PermissionDenied, /** The user is not permitted to perform the requested + * operation. + */ + NotAvailable, /** Raised when the requested functionality is temporarily + * unavailable. + */ + NotImplemented, /** Raised when the requested method, channel, etc is not + * available on this connection. + */ +} TelepathyErrors; + +GQuark telepathy_errors_quark (void); +#define TELEPATHY_ERRORS telepathy_errors_quark () + +G_END_DECLS + +#endif /* #ifndef __TELEPATHY_ERRORS_H__*/ diff --git a/src/telepathy-helpers.c b/src/telepathy-helpers.c new file mode 100644 index 0000000..5c8cf18 --- /dev/null +++ b/src/telepathy-helpers.c @@ -0,0 +1,63 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 <dbus/dbus-glib.h> +#include "telepathy-helpers.h" + +DBusGConnection * +tp_get_bus () +{ + static DBusGConnection *bus = NULL; + + if (bus == NULL) + { + GError *error = NULL; + + bus = dbus_g_bus_get (DBUS_BUS_STARTER, &error); + + if (bus == NULL) + g_error ("Failed to connect to starter bus: %s", error->message); + } + + return bus; +} + +DBusGProxy * +tp_get_bus_proxy () +{ + static DBusGProxy *bus_proxy = NULL; + + if (bus_proxy == NULL) + { + DBusGConnection *bus = tp_get_bus (); + + bus_proxy = dbus_g_proxy_new_for_name (bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus"); + + if (bus_proxy == NULL) + g_error ("Failed to get proxy object for bus."); + } + + return bus_proxy; +} + + diff --git a/src/telepathy-helpers.h b/src/telepathy-helpers.h new file mode 100644 index 0000000..234e898 --- /dev/null +++ b/src/telepathy-helpers.h @@ -0,0 +1,34 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __TELEPATHY_HELPERS_H__ +#define __TELEPATHY_HELPERS_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +DBusGConnection * tp_get_bus (); +DBusGProxy * tp_get_bus_proxy (); + +G_END_DECLS + +#endif /* __TELEPATHY_HELPERS_H__ */ + diff --git a/src/telepathy-interfaces.h b/src/telepathy-interfaces.h new file mode 100644 index 0000000..c3e1563 --- /dev/null +++ b/src/telepathy-interfaces.h @@ -0,0 +1,65 @@ +/* + * This file is part of telepathy-idle + * + * Copyright (C) 2006 Nokia Corporation. All rights reserved. + * + * + * 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 __TELEPATHY_INTERFACES_H__ +#define __TELEPATHY_INTERFACES_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define TP_IFACE_CHANNEL_INTERFACE \ + "org.freedesktop.Telepathy.Channel" +#define TP_IFACE_CHANNEL_INTERFACE_GROUP \ + "org.freedesktop.Telepathy.Channel.Interface.Group" +#define TP_IFACE_CHANNEL_INTERFACE_HOLD \ + "org.freedesktop.Telepathy.Channel.Interface.Hold" +#define TP_IFACE_CHANNEL_INTERFACE_PASSWORD \ + "org.freedesktop.Telepathy.Channel.Interface.Password" +#define TP_IFACE_CHANNEL_INTERFACE_TRANSFER \ + "org.freedesktop.Telepathy.Channel.Interface.Transfer" +#define TP_IFACE_CHANNEL_TYPE_ROOM_LIST \ + "org.freedesktop.Telepathy.Channel.Type.RoomList" +#define TP_IFACE_CHANNEL_TYPE_TEXT \ + "org.freedesktop.Telepathy.Channel.Type.Text" +#define TP_IFACE_CONN_INTERFACE \ + "org.freedesktop.Telepathy.Connection" +#define TP_IFACE_CONN_INTERFACE_ALIASING \ + "org.freedesktop.Telepathy.Connection.Interface.Aliasing" +#define TP_IFACE_CONN_INTERFACE_CAPABILITIES \ + "org.freedesktop.Telepathy.Connection.Interface.Capabilities" +#define TP_IFACE_CONN_INTERFACE_CONTACT_INFO \ + "org.freedesktop.Telepathy.Connection.Interface.ContactInfo" +#define TP_IFACE_CONN_INTERFACE_FORWARDING \ + "org.freedesktop.Telepathy.Connection.Interface.Forwarding" +#define TP_IFACE_CONN_INTERFACE_PRESENCE \ + "org.freedesktop.Telepathy.Connection.Interface.Presence" +#define TP_IFACE_CONN_INTERFACE_PRIVACY \ + "org.freedesktop.Telepathy.Connection.Interface.Privacy" +#define TP_IFACE_CONN_INTERFACE_RENAMING \ + "org.freedesktop.Telepathy.Connection.Interface.Renaming" +#define TP_IFACE_CONN_MGR_INTERFACE \ + "org.freedesktop.Telepathy.ConnectionManager" +#define TP_IFACE_PROPERTIES \ + "org.freedesktop.Telepathy.Properties" + +G_END_DECLS + +#endif /* #ifndef __TELEPATHY_INTERFACES_H__*/ |