├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── config.h.in ├── create-deb.sh ├── debian-control ├── evolution-decsync-add-collections.sh ├── meson.build └── src ├── backends ├── addressbook │ ├── e-book-backend-decsync-factory.c │ ├── e-book-backend-decsync.c │ ├── e-book-backend-decsync.h │ └── meson.build ├── calendar │ ├── e-cal-backend-decsync-events.c │ ├── e-cal-backend-decsync-events.h │ ├── e-cal-backend-decsync-factory.c │ ├── e-cal-backend-decsync-journal.c │ ├── e-cal-backend-decsync-journal.h │ ├── e-cal-backend-decsync-todos.c │ ├── e-cal-backend-decsync-todos.h │ ├── e-cal-backend-decsync.c │ ├── e-cal-backend-decsync.h │ └── meson.build └── meson.build ├── e-source ├── decsync.source ├── e-source-decsync.c ├── e-source-decsync.h └── meson.build ├── meson.build └── modules ├── book-config ├── meson.build └── module-book-config-decsync.c ├── cal-config ├── meson.build └── module-cal-config-decsync.c ├── meson.build └── utils ├── decsync.c └── decsync.h /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | create-deb: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - run: sudo apt install meson ninja-build libjson-glib-dev libebook1.2-dev libedata-book1.2-dev libedata-cal2.0-dev evolution-dev 14 | - run: git clone https://github.com/39aldo39/libdecsync && cd ./libdecsync && git checkout v2.2.1 && make && sudo make install 15 | - run: meson build 16 | - run: ninja -C build 17 | - run: ./create-deb.sh 18 | - uses: actions/upload-artifact@v2 19 | with: 20 | name: evolution-decsync.deb 21 | path: evolution-decsync.deb 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | _build 3 | .build 4 | builddir 5 | deb 6 | *.deb 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LIBRARY GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the library GPL. It is 10 | numbered 2 because it goes with version 2 of the ordinary GPL.] 11 | 12 | Preamble 13 | 14 | The licenses for most software are designed to take away your 15 | freedom to share and change it. By contrast, the GNU General Public 16 | Licenses are intended to guarantee your freedom to share and change 17 | free software--to make sure the software is free for all its users. 18 | 19 | This license, the Library General Public License, applies to some 20 | specially designated Free Software Foundation software, and to any 21 | other libraries whose authors decide to use it. You can use it for 22 | your libraries, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | this service if you wish), that you receive source code or can get it 28 | if you want it, that you can change the software or use pieces of it 29 | in new free programs; and that you know you can do these things. 30 | 31 | To protect your rights, we need to make restrictions that forbid 32 | anyone to deny you these rights or to ask you to surrender the rights. 33 | These restrictions translate to certain responsibilities for you if 34 | you distribute copies of the library, or if you modify it. 35 | 36 | For example, if you distribute copies of the library, whether gratis 37 | or for a fee, you must give the recipients all the rights that we gave 38 | you. You must make sure that they, too, receive or can get the source 39 | code. If you link a program with the library, you must provide 40 | complete object files to the recipients so that they can relink them 41 | with the library, after making changes to the library and recompiling 42 | it. And you must show them these terms so they know their rights. 43 | 44 | Our method of protecting your rights has two steps: (1) copyright 45 | the library, and (2) offer you this license which gives you legal 46 | permission to copy, distribute and/or modify the library. 47 | 48 | Also, for each distributor's protection, we want to make certain 49 | that everyone understands that there is no warranty for this free 50 | library. If the library is modified by someone else and passed on, we 51 | want its recipients to know that what they have is not the original 52 | version, so that any problems introduced by others will not reflect on 53 | the original authors' reputations. 54 | 55 | Finally, any free program is threatened constantly by software 56 | patents. We wish to avoid the danger that companies distributing free 57 | software will individually obtain patent licenses, thus in effect 58 | transforming the program into proprietary software. To prevent this, 59 | we have made it clear that any patent must be licensed for everyone's 60 | free use or not licensed at all. 61 | 62 | Most GNU software, including some libraries, is covered by the ordinary 63 | GNU General Public License, which was designed for utility programs. This 64 | license, the GNU Library General Public License, applies to certain 65 | designated libraries. This license is quite different from the ordinary 66 | one; be sure to read it in full, and don't assume that anything in it is 67 | the same as in the ordinary license. 68 | 69 | The reason we have a separate public license for some libraries is that 70 | they blur the distinction we usually make between modifying or adding to a 71 | program and simply using it. Linking a program with a library, without 72 | changing the library, is in some sense simply using the library, and is 73 | analogous to running a utility program or application program. However, in 74 | a textual and legal sense, the linked executable is a combined work, a 75 | derivative of the original library, and the ordinary General Public License 76 | treats it as such. 77 | 78 | Because of this blurred distinction, using the ordinary General 79 | Public License for libraries did not effectively promote software 80 | sharing, because most developers did not use the libraries. We 81 | concluded that weaker conditions might promote sharing better. 82 | 83 | However, unrestricted linking of non-free programs would deprive the 84 | users of those programs of all benefit from the free status of the 85 | libraries themselves. This Library General Public License is intended to 86 | permit developers of non-free programs to use free libraries, while 87 | preserving your freedom as a user of such programs to change the free 88 | libraries that are incorporated in them. (We have not seen how to achieve 89 | this as regards changes in header files, but we have achieved it as regards 90 | changes in the actual functions of the Library.) The hope is that this 91 | will lead to faster development of free libraries. 92 | 93 | The precise terms and conditions for copying, distribution and 94 | modification follow. Pay close attention to the difference between a 95 | "work based on the library" and a "work that uses the library". The 96 | former contains code derived from the library, while the latter only 97 | works together with the library. 98 | 99 | Note that it is possible for a library to be covered by the ordinary 100 | General Public License rather than by this special one. 101 | 102 | GNU LIBRARY GENERAL PUBLIC LICENSE 103 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 104 | 105 | 0. This License Agreement applies to any software library which 106 | contains a notice placed by the copyright holder or other authorized 107 | party saying it may be distributed under the terms of this Library 108 | General Public License (also called "this License"). Each licensee is 109 | addressed as "you". 110 | 111 | A "library" means a collection of software functions and/or data 112 | prepared so as to be conveniently linked with application programs 113 | (which use some of those functions and data) to form executables. 114 | 115 | The "Library", below, refers to any such software library or work 116 | which has been distributed under these terms. A "work based on the 117 | Library" means either the Library or any derivative work under 118 | copyright law: that is to say, a work containing the Library or a 119 | portion of it, either verbatim or with modifications and/or translated 120 | straightforwardly into another language. (Hereinafter, translation is 121 | included without limitation in the term "modification".) 122 | 123 | "Source code" for a work means the preferred form of the work for 124 | making modifications to it. For a library, complete source code means 125 | all the source code for all modules it contains, plus any associated 126 | interface definition files, plus the scripts used to control compilation 127 | and installation of the library. 128 | 129 | Activities other than copying, distribution and modification are not 130 | covered by this License; they are outside its scope. The act of 131 | running a program using the Library is not restricted, and output from 132 | such a program is covered only if its contents constitute a work based 133 | on the Library (independent of the use of the Library in a tool for 134 | writing it). Whether that is true depends on what the Library does 135 | and what the program that uses the Library does. 136 | 137 | 1. You may copy and distribute verbatim copies of the Library's 138 | complete source code as you receive it, in any medium, provided that 139 | you conspicuously and appropriately publish on each copy an 140 | appropriate copyright notice and disclaimer of warranty; keep intact 141 | all the notices that refer to this License and to the absence of any 142 | warranty; and distribute a copy of this License along with the 143 | Library. 144 | 145 | You may charge a fee for the physical act of transferring a copy, 146 | and you may at your option offer warranty protection in exchange for a 147 | fee. 148 | 149 | 2. You may modify your copy or copies of the Library or any portion 150 | of it, thus forming a work based on the Library, and copy and 151 | distribute such modifications or work under the terms of Section 1 152 | above, provided that you also meet all of these conditions: 153 | 154 | a) The modified work must itself be a software library. 155 | 156 | b) You must cause the files modified to carry prominent notices 157 | stating that you changed the files and the date of any change. 158 | 159 | c) You must cause the whole of the work to be licensed at no 160 | charge to all third parties under the terms of this License. 161 | 162 | d) If a facility in the modified Library refers to a function or a 163 | table of data to be supplied by an application program that uses 164 | the facility, other than as an argument passed when the facility 165 | is invoked, then you must make a good faith effort to ensure that, 166 | in the event an application does not supply such function or 167 | table, the facility still operates, and performs whatever part of 168 | its purpose remains meaningful. 169 | 170 | (For example, a function in a library to compute square roots has 171 | a purpose that is entirely well-defined independent of the 172 | application. Therefore, Subsection 2d requires that any 173 | application-supplied function or table used by this function must 174 | be optional: if the application does not supply it, the square 175 | root function must still compute square roots.) 176 | 177 | These requirements apply to the modified work as a whole. If 178 | identifiable sections of that work are not derived from the Library, 179 | and can be reasonably considered independent and separate works in 180 | themselves, then this License, and its terms, do not apply to those 181 | sections when you distribute them as separate works. But when you 182 | distribute the same sections as part of a whole which is a work based 183 | on the Library, the distribution of the whole must be on the terms of 184 | this License, whose permissions for other licensees extend to the 185 | entire whole, and thus to each and every part regardless of who wrote 186 | it. 187 | 188 | Thus, it is not the intent of this section to claim rights or contest 189 | your rights to work written entirely by you; rather, the intent is to 190 | exercise the right to control the distribution of derivative or 191 | collective works based on the Library. 192 | 193 | In addition, mere aggregation of another work not based on the Library 194 | with the Library (or with a work based on the Library) on a volume of 195 | a storage or distribution medium does not bring the other work under 196 | the scope of this License. 197 | 198 | 3. You may opt to apply the terms of the ordinary GNU General Public 199 | License instead of this License to a given copy of the Library. To do 200 | this, you must alter all the notices that refer to this License, so 201 | that they refer to the ordinary GNU General Public License, version 2, 202 | instead of to this License. (If a newer version than version 2 of the 203 | ordinary GNU General Public License has appeared, then you can specify 204 | that version instead if you wish.) Do not make any other change in 205 | these notices. 206 | 207 | Once this change is made in a given copy, it is irreversible for 208 | that copy, so the ordinary GNU General Public License applies to all 209 | subsequent copies and derivative works made from that copy. 210 | 211 | This option is useful when you wish to copy part of the code of 212 | the Library into a program that is not a library. 213 | 214 | 4. You may copy and distribute the Library (or a portion or 215 | derivative of it, under Section 2) in object code or executable form 216 | under the terms of Sections 1 and 2 above provided that you accompany 217 | it with the complete corresponding machine-readable source code, which 218 | must be distributed under the terms of Sections 1 and 2 above on a 219 | medium customarily used for software interchange. 220 | 221 | If distribution of object code is made by offering access to copy 222 | from a designated place, then offering equivalent access to copy the 223 | source code from the same place satisfies the requirement to 224 | distribute the source code, even though third parties are not 225 | compelled to copy the source along with the object code. 226 | 227 | 5. A program that contains no derivative of any portion of the 228 | Library, but is designed to work with the Library by being compiled or 229 | linked with it, is called a "work that uses the Library". Such a 230 | work, in isolation, is not a derivative work of the Library, and 231 | therefore falls outside the scope of this License. 232 | 233 | However, linking a "work that uses the Library" with the Library 234 | creates an executable that is a derivative of the Library (because it 235 | contains portions of the Library), rather than a "work that uses the 236 | library". The executable is therefore covered by this License. 237 | Section 6 states terms for distribution of such executables. 238 | 239 | When a "work that uses the Library" uses material from a header file 240 | that is part of the Library, the object code for the work may be a 241 | derivative work of the Library even though the source code is not. 242 | Whether this is true is especially significant if the work can be 243 | linked without the Library, or if the work is itself a library. The 244 | threshold for this to be true is not precisely defined by law. 245 | 246 | If such an object file uses only numerical parameters, data 247 | structure layouts and accessors, and small macros and small inline 248 | functions (ten lines or less in length), then the use of the object 249 | file is unrestricted, regardless of whether it is legally a derivative 250 | work. (Executables containing this object code plus portions of the 251 | Library will still fall under Section 6.) 252 | 253 | Otherwise, if the work is a derivative of the Library, you may 254 | distribute the object code for the work under the terms of Section 6. 255 | Any executables containing that work also fall under Section 6, 256 | whether or not they are linked directly with the Library itself. 257 | 258 | 6. As an exception to the Sections above, you may also compile or 259 | link a "work that uses the Library" with the Library to produce a 260 | work containing portions of the Library, and distribute that work 261 | under terms of your choice, provided that the terms permit 262 | modification of the work for the customer's own use and reverse 263 | engineering for debugging such modifications. 264 | 265 | You must give prominent notice with each copy of the work that the 266 | Library is used in it and that the Library and its use are covered by 267 | this License. You must supply a copy of this License. If the work 268 | during execution displays copyright notices, you must include the 269 | copyright notice for the Library among them, as well as a reference 270 | directing the user to the copy of this License. Also, you must do one 271 | of these things: 272 | 273 | a) Accompany the work with the complete corresponding 274 | machine-readable source code for the Library including whatever 275 | changes were used in the work (which must be distributed under 276 | Sections 1 and 2 above); and, if the work is an executable linked 277 | with the Library, with the complete machine-readable "work that 278 | uses the Library", as object code and/or source code, so that the 279 | user can modify the Library and then relink to produce a modified 280 | executable containing the modified Library. (It is understood 281 | that the user who changes the contents of definitions files in the 282 | Library will not necessarily be able to recompile the application 283 | to use the modified definitions.) 284 | 285 | b) Accompany the work with a written offer, valid for at 286 | least three years, to give the same user the materials 287 | specified in Subsection 6a, above, for a charge no more 288 | than the cost of performing this distribution. 289 | 290 | c) If distribution of the work is made by offering access to copy 291 | from a designated place, offer equivalent access to copy the above 292 | specified materials from the same place. 293 | 294 | d) Verify that the user has already received a copy of these 295 | materials or that you have already sent this user a copy. 296 | 297 | For an executable, the required form of the "work that uses the 298 | Library" must include any data and utility programs needed for 299 | reproducing the executable from it. However, as a special exception, 300 | the source code distributed need not include anything that is normally 301 | distributed (in either source or binary form) with the major 302 | components (compiler, kernel, and so on) of the operating system on 303 | which the executable runs, unless that component itself accompanies 304 | the executable. 305 | 306 | It may happen that this requirement contradicts the license 307 | restrictions of other proprietary libraries that do not normally 308 | accompany the operating system. Such a contradiction means you cannot 309 | use both them and the Library together in an executable that you 310 | distribute. 311 | 312 | 7. You may place library facilities that are a work based on the 313 | Library side-by-side in a single library together with other library 314 | facilities not covered by this License, and distribute such a combined 315 | library, provided that the separate distribution of the work based on 316 | the Library and of the other library facilities is otherwise 317 | permitted, and provided that you do these two things: 318 | 319 | a) Accompany the combined library with a copy of the same work 320 | based on the Library, uncombined with any other library 321 | facilities. This must be distributed under the terms of the 322 | Sections above. 323 | 324 | b) Give prominent notice with the combined library of the fact 325 | that part of it is a work based on the Library, and explaining 326 | where to find the accompanying uncombined form of the same work. 327 | 328 | 8. You may not copy, modify, sublicense, link with, or distribute 329 | the Library except as expressly provided under this License. Any 330 | attempt otherwise to copy, modify, sublicense, link with, or 331 | distribute the Library is void, and will automatically terminate your 332 | rights under this License. However, parties who have received copies, 333 | or rights, from you under this License will not have their licenses 334 | terminated so long as such parties remain in full compliance. 335 | 336 | 9. You are not required to accept this License, since you have not 337 | signed it. However, nothing else grants you permission to modify or 338 | distribute the Library or its derivative works. These actions are 339 | prohibited by law if you do not accept this License. Therefore, by 340 | modifying or distributing the Library (or any work based on the 341 | Library), you indicate your acceptance of this License to do so, and 342 | all its terms and conditions for copying, distributing or modifying 343 | the Library or works based on it. 344 | 345 | 10. Each time you redistribute the Library (or any work based on the 346 | Library), the recipient automatically receives a license from the 347 | original licensor to copy, distribute, link with or modify the Library 348 | subject to these terms and conditions. You may not impose any further 349 | restrictions on the recipients' exercise of the rights granted herein. 350 | You are not responsible for enforcing compliance by third parties to 351 | this License. 352 | 353 | 11. If, as a consequence of a court judgment or allegation of patent 354 | infringement or for any other reason (not limited to patent issues), 355 | conditions are imposed on you (whether by court order, agreement or 356 | otherwise) that contradict the conditions of this License, they do not 357 | excuse you from the conditions of this License. If you cannot 358 | distribute so as to satisfy simultaneously your obligations under this 359 | License and any other pertinent obligations, then as a consequence you 360 | may not distribute the Library at all. For example, if a patent 361 | license would not permit royalty-free redistribution of the Library by 362 | all those who receive copies directly or indirectly through you, then 363 | the only way you could satisfy both it and this License would be to 364 | refrain entirely from distribution of the Library. 365 | 366 | If any portion of this section is held invalid or unenforceable under any 367 | particular circumstance, the balance of the section is intended to apply, 368 | and the section as a whole is intended to apply in other circumstances. 369 | 370 | It is not the purpose of this section to induce you to infringe any 371 | patents or other property right claims or to contest validity of any 372 | such claims; this section has the sole purpose of protecting the 373 | integrity of the free software distribution system which is 374 | implemented by public license practices. Many people have made 375 | generous contributions to the wide range of software distributed 376 | through that system in reliance on consistent application of that 377 | system; it is up to the author/donor to decide if he or she is willing 378 | to distribute software through any other system and a licensee cannot 379 | impose that choice. 380 | 381 | This section is intended to make thoroughly clear what is believed to 382 | be a consequence of the rest of this License. 383 | 384 | 12. If the distribution and/or use of the Library is restricted in 385 | certain countries either by patents or by copyrighted interfaces, the 386 | original copyright holder who places the Library under this License may add 387 | an explicit geographical distribution limitation excluding those countries, 388 | so that distribution is permitted only in or among countries not thus 389 | excluded. In such case, this License incorporates the limitation as if 390 | written in the body of this License. 391 | 392 | 13. The Free Software Foundation may publish revised and/or new 393 | versions of the Library General Public License from time to time. 394 | Such new versions will be similar in spirit to the present version, 395 | but may differ in detail to address new problems or concerns. 396 | 397 | Each version is given a distinguishing version number. If the Library 398 | specifies a version number of this License which applies to it and 399 | "any later version", you have the option of following the terms and 400 | conditions either of that version or of any later version published by 401 | the Free Software Foundation. If the Library does not specify a 402 | license version number, you may choose any version ever published by 403 | the Free Software Foundation. 404 | 405 | 14. If you wish to incorporate parts of the Library into other free 406 | programs whose distribution conditions are incompatible with these, 407 | write to the author to ask for permission. For software which is 408 | copyrighted by the Free Software Foundation, write to the Free 409 | Software Foundation; we sometimes make exceptions for this. Our 410 | decision will be guided by the two goals of preserving the free status 411 | of all derivatives of our free software and of promoting the sharing 412 | and reuse of software generally. 413 | 414 | NO WARRANTY 415 | 416 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 417 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 418 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 419 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 420 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 421 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 422 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 423 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 424 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 425 | 426 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 427 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 428 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 429 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 430 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 431 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 432 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 433 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 434 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 435 | DAMAGES. 436 | 437 | END OF TERMS AND CONDITIONS 438 | 439 | How to Apply These Terms to Your New Libraries 440 | 441 | If you develop a new library, and you want it to be of the greatest 442 | possible use to the public, we recommend making it free software that 443 | everyone can redistribute and change. You can do so by permitting 444 | redistribution under these terms (or, alternatively, under the terms of the 445 | ordinary General Public License). 446 | 447 | To apply these terms, attach the following notices to the library. It is 448 | safest to attach them to the start of each source file to most effectively 449 | convey the exclusion of warranty; and each file should have at least the 450 | "copyright" line and a pointer to where the full notice is found. 451 | 452 | 453 | Copyright (C) 454 | 455 | This library is free software; you can redistribute it and/or 456 | modify it under the terms of the GNU Library General Public 457 | License as published by the Free Software Foundation; either 458 | version 2 of the License, or (at your option) any later version. 459 | 460 | This library is distributed in the hope that it will be useful, 461 | but WITHOUT ANY WARRANTY; without even the implied warranty of 462 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 463 | Library General Public License for more details. 464 | 465 | You should have received a copy of the GNU Library General Public 466 | License along with this library; if not, write to the 467 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 468 | Boston, MA 02110-1301, USA 469 | 470 | Also add information on how to contact you by electronic and paper mail. 471 | 472 | You should also get your employer (if you work as a programmer) or your 473 | school, if any, to sign a "copyright disclaimer" for the library, if 474 | necessary. Here is a sample; alter the names: 475 | 476 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 477 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 478 | 479 | , 1 April 1990 480 | Ty Coon, President of Vice 481 | 482 | That's all there is to it! 483 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DecSync for Evolution 2 | ===================== 3 | 4 | DecSync for Evolution is an [Evolution](https://wiki.gnome.org/Apps/Evolution) plugin which synchronizes contacts and calendars using [DecSync](https://github.com/39aldo39/DecSync). To start synchronizing, all you have to do is synchronize the DecSync directory (by default `~/.local/share/decsync`), using for example [Syncthing](https://syncthing.net). 5 | 6 | Installation 7 | ------------ 8 | 9 | ### Debian/Ubuntu 10 | For Debian/Ubuntu, there is a `.deb` package available at the [releases page](https://github.com/39aldo39/Evolution-DecSync/releases). 11 | 12 | ### Arch Linux 13 | For Arch Linux, there is a [AUR package](https://aur.archlinux.org/packages/evolution-decsync) available, created by vasket. 14 | 15 | Build from source 16 | ----------------- 17 | 18 | In addition to the dependencies below, the library [libdecsync](https://github.com/39aldo39/libdecsync) is also required. 19 | 20 | Install dependencies: 21 | 22 | ### Debian/Ubuntu 23 | 24 | ``` 25 | sudo apt install \ 26 | build-essential \ 27 | git \ 28 | meson \ 29 | ninja-build \ 30 | pkg-config \ 31 | libjson-glib-dev \ 32 | libebook1.2-dev \ 33 | libedata-book1.2-dev \ 34 | libedata-cal2.0-dev \ 35 | evolution-dev 36 | ``` 37 | 38 | ### Fedora 39 | ``` 40 | sudo dnf install \ 41 | cmake \ 42 | git \ 43 | gcc \ 44 | meson \ 45 | json-glib-devel \ 46 | evolution-data-server-devel \ 47 | evolution-devel 48 | ``` 49 | 50 | ### Arch Linux 51 | 52 | ``` 53 | sudo pacman -S \ 54 | cmake \ 55 | meson \ 56 | ninja \ 57 | json-glib \ 58 | evolution-data-server \ 59 | evolution 60 | ``` 61 | 62 | ### Build 63 | 64 | ``` 65 | git clone --recursive https://github.com/39aldo39/Evolution-DecSync 66 | cd ./Evolution-DecSync 67 | meson build 68 | sudo ninja -C build install 69 | ``` 70 | 71 | Note: the master branch does not work for older versions of Evolution. For those, use the `evolution-3.40` branch, or similar, instead. 72 | 73 | ### Further steps 74 | 75 | Once building is complete, enable syncing with Evolution by: 76 | 77 | 1. Restart your computer 78 | 2. Open Evolution 79 | 3. Click `Edit` → `Accounts` 80 | 4. In the window that opens, you can use `Add` on the right-hand side to add an `Address Book` or `Calendar`. 81 | 5. For type, select `DecSync`; eventually check `Mark as default address book/calendar`. If applicable, select the location of your DecSync folder. 82 | 6. A new address book will appear. Right-click it and select `Refresh` to instantly show your contacts/events. 83 | 84 | All done! 85 | 86 | 87 | Donations 88 | --------- 89 | 90 | ### PayPal 91 | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4V96AFD3S4TPJ) 92 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* Name of the package */ 2 | #define PACKAGE "@PROJECT_NAME@" 3 | 4 | /* Package name for gettext */ 5 | #define GETTEXT_PACKAGE "@PROJECT_NAME@" 6 | 7 | /* ESource install directory */ 8 | #define E_SOURCE_DIR "@E_SOURCE_DIR@" 9 | -------------------------------------------------------------------------------- /create-deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | DESTDIR="$(pwd)/deb" ninja -C build install 5 | 6 | mkdir -p deb/DEBIAN 7 | cp debian-control deb/DEBIAN/control 8 | 9 | dpkg-deb -Zxz --build deb evolution-decsync.deb 10 | -------------------------------------------------------------------------------- /debian-control: -------------------------------------------------------------------------------- 1 | Package: evolution-decsync 2 | Version: 2.1.0-3-evolution-3.44 3 | Architecture: amd64 4 | Maintainer: Aldo Gunsing 5 | Homepage: https://github.com/39aldo39/Evolution-DecSync 6 | Depends: evolution (>= 3.44), libjson-glib-1.0-0, libdecsync (>= 2.0.1) 7 | Description: DecSync synchronization for Evolution 8 | DecSync for Evolution is an Evolution plugin which synchronizes contacts and calendars using DecSync. 9 | To start synchronizing, all you have to do is synchronize the DecSync directory (by default ~/.local/share/decsync), using for example Syncthing. 10 | -------------------------------------------------------------------------------- /evolution-decsync-add-collections.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | config_dir="${XDG_CONFIG_HOME:-$HOME/.config}" 5 | source_dir="$config_dir/evolution/sources" 6 | decsync_dir="${DECSYNC_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/decsync}" 7 | app_id="$(hostname)-Evolution-$(shuf -i 00000-99999 -n1)" 8 | interval=5 9 | 10 | OPTIND=1 11 | 12 | while getopts "i:" opt; do 13 | case "$opt" in 14 | i) interval="$OPTARG";; 15 | *) exit 1;; 16 | esac 17 | done 18 | 19 | # Add main DecSync source file, if necessary 20 | decsync_source="[Data Source] 21 | DisplayName=DecSync 22 | Enabled=true 23 | Parent= 24 | " 25 | if ! [ -f "$source_dir/decsync.source" ]; then 26 | echo "$decsync_source" > "$source_dir/decsync.source" 27 | fi 28 | 29 | # Add the source file of a calendar, if necessary 30 | add_calendar() { 31 | collection=$1 32 | name=$collection 33 | 34 | collection_source="[Data Source] 35 | DisplayName=$name 36 | Enabled=true 37 | Parent=decsync 38 | 39 | [DecSync Backend] 40 | DecsyncDir=$decsync_dir 41 | Collection=$collection 42 | AppId=$app_id 43 | 44 | [Refresh] 45 | Enabled=true 46 | IntervalMinutes=$interval 47 | 48 | [Calendar] 49 | BackendName=decsync 50 | Selected=true 51 | 52 | [Offline] 53 | StaySynchronized=true 54 | 55 | [Alarms] 56 | IncludeMe=true 57 | 58 | [Conflict Search] 59 | IncludeMe=true 60 | " 61 | if ! [ -f "$source_dir/decsync-calendar-$collection.source" ]; then 62 | echo "$collection_source" > "$source_dir/decsync-calendar-$collection.source" 63 | echo "Calendar $collection added" 64 | else 65 | echo "Calendar $collection already exists" 66 | fi 67 | } 68 | 69 | # Add the source file of a addressbook, if necessary 70 | add_addressbook() { 71 | collection=$1 72 | name=$collection 73 | 74 | collection_source="[Data Source] 75 | DisplayName=$name 76 | Enabled=true 77 | Parent=decsync 78 | 79 | [DecSync Backend] 80 | DecsyncDir=$decsync_dir 81 | Collection=$collection 82 | AppId=$app_id 83 | 84 | [Refresh] 85 | Enabled=true 86 | IntervalMinutes=$interval 87 | 88 | [Contacts Backend] 89 | IncludeMe=true 90 | 91 | [Address Book] 92 | BackendName=decsync 93 | 94 | [Autocomplete] 95 | IncludeMe=true 96 | " 97 | if ! [ -f "$source_dir/decsync-addressbook-$collection.source" ]; then 98 | echo "$collection_source" > "$source_dir/decsync-addressbook-$collection.source" 99 | echo "Addressbook $collection added" 100 | else 101 | echo "Addressbook $collection already exists" 102 | fi 103 | } 104 | 105 | # Add calendar collections 106 | for collection in "$decsync_dir/calendars/"*; do 107 | collection=$(basename "$collection") 108 | if [ "$collection" = "*" ]; then 109 | echo "No calendars found in $decsync_dir" 110 | break 111 | fi; 112 | add_calendar "$collection" 113 | done 114 | 115 | # Add addressbook collections 116 | for collection in "$decsync_dir/contacts/"*; do 117 | collection=$(basename "$collection") 118 | if [ "$collection" = "*" ]; then 119 | echo "No addressbooks found in $decsync_dir" 120 | break 121 | fi; 122 | add_addressbook "$collection" 123 | done 124 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('evolution-decsync', ['c'], 2 | version: '2.1.0-3-evolution-3.44', 3 | meson_version: '>= 0.44.1', 4 | license: 'LGPL', 5 | default_options: ['prefix=/usr'] 6 | ) 7 | 8 | # Dependencies 9 | libedatabook = dependency('libedata-book-1.2', version: '>=3.44') 10 | libedatacal = dependency('libedata-cal-2.0', version: '>=3.44') 11 | evolutionshell = dependency('evolution-shell-3.0', version: '>=3.44') 12 | json_glib = dependency('json-glib-1.0') 13 | libdecsync = dependency('decsync', version: '>=2.0.1') 14 | 15 | # Special directories 16 | LIB_INSTALL_DIR = join_paths(get_option('prefix'), 'lib') 17 | SHARE_INSTALL_PREFIX = join_paths(get_option('prefix'), 'share') 18 | 19 | privlibdir = join_paths(LIB_INSTALL_DIR, meson.project_name()) 20 | moduledir = join_paths(privlibdir, 'registry-modules') 21 | privsharedir = join_paths(SHARE_INSTALL_PREFIX, meson.project_name()) 22 | 23 | E_SOURCE_DIR = join_paths(privsharedir, 'sources') 24 | 25 | ebook_backenddir = libedatabook.get_pkgconfig_variable('backenddir') 26 | ecal_backenddir = libedatacal.get_pkgconfig_variable('backenddir') 27 | evo_moduledir = evolutionshell.get_pkgconfig_variable('moduledir') 28 | 29 | # Generate the ${PROJECT_NAME}-config.h file 30 | conf_data = configuration_data() 31 | conf_data.set('PROJECT_NAME', meson.project_name()) 32 | conf_data.set('E_SOURCE_DIR', E_SOURCE_DIR) 33 | configure_file( 34 | input: 'config.h.in', 35 | output: meson.project_name() + '-config.h', 36 | configuration: conf_data 37 | ) 38 | 39 | subdir('src') 40 | -------------------------------------------------------------------------------- /src/backends/addressbook/e-book-backend-decsync-factory.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ 2 | 3 | /* e-book-backend-decsync-factory.c - DecSync contact backend factory. 4 | * Based on the file contact backend factory. 5 | * 6 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 7 | * Copyright (C) 2018 Aldo Gunsing 8 | * 9 | * This library is free software: you can redistribute it and/or modify it 10 | * under the terms of the GNU Lesser General Public License as published by 11 | * the Free Software Foundation. 12 | * 13 | * This library is distributed in the hope that it will be useful, but 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 15 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 16 | * for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library. If not, see . 20 | * 21 | * Authors: Chris Toshok 22 | */ 23 | 24 | #include "evolution-decsync-config.h" 25 | 26 | #include "e-book-backend-decsync.h" 27 | 28 | #define FACTORY_NAME "decsync" 29 | 30 | typedef EBookBackendFactory EBookBackendDecsyncFactory; 31 | typedef EBookBackendFactoryClass EBookBackendDecsyncFactoryClass; 32 | 33 | static EModule *e_module; 34 | 35 | /* Module Entry Points */ 36 | void e_module_load (GTypeModule *type_module); 37 | void e_module_unload (GTypeModule *type_module); 38 | 39 | /* Forward Declarations */ 40 | GType e_book_backend_decsync_factory_get_type (void); 41 | 42 | G_DEFINE_DYNAMIC_TYPE ( 43 | EBookBackendDecsyncFactory, 44 | e_book_backend_decsync_factory, 45 | E_TYPE_BOOK_BACKEND_FACTORY) 46 | 47 | static void 48 | e_book_backend_decsync_factory_class_init (EBookBackendFactoryClass *class) 49 | { 50 | EBackendFactoryClass *backend_factory_class; 51 | 52 | backend_factory_class = E_BACKEND_FACTORY_CLASS (class); 53 | backend_factory_class->e_module = e_module; 54 | backend_factory_class->share_subprocess = TRUE; 55 | 56 | class->factory_name = FACTORY_NAME; 57 | class->backend_type = E_TYPE_BOOK_BACKEND_DECSYNC; 58 | } 59 | 60 | static void 61 | e_book_backend_decsync_factory_class_finalize (EBookBackendFactoryClass *class) 62 | { 63 | } 64 | 65 | static void 66 | e_book_backend_decsync_factory_init (EBookBackendFactory *factory) 67 | { 68 | } 69 | 70 | G_MODULE_EXPORT void 71 | e_module_load (GTypeModule *type_module) 72 | { 73 | e_module = E_MODULE (type_module); 74 | 75 | e_book_backend_decsync_factory_register_type (type_module); 76 | } 77 | 78 | G_MODULE_EXPORT void 79 | e_module_unload (GTypeModule *type_module) 80 | { 81 | e_module = NULL; 82 | } 83 | -------------------------------------------------------------------------------- /src/backends/addressbook/e-book-backend-decsync.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ 2 | 3 | /* e-book-backend-decsync.c - DecSync contact backend. 4 | * Based on the file contact backend. 5 | * 6 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 7 | * Copyright (C) 2012 Intel Corporation 8 | * Copyright (C) 2018 Aldo Gunsing 9 | * 10 | * This library is free software: you can redistribute it and/or modify it 11 | * under the terms of the GNU Lesser General Public License as published by 12 | * the Free Software Foundation. 13 | * 14 | * This library is distributed in the hope that it will be useful, but 15 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 17 | * for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public License 20 | * along with this library. If not, see . 21 | * 22 | * Authors: Nat Friedman 23 | * Chris Toshok 24 | * Hans Petter Jansson 25 | * Tristan Van Berkom 26 | */ 27 | 28 | #include "evolution-decsync-config.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | 47 | #include "e-book-backend-decsync.h" 48 | 49 | #define d(x) 50 | 51 | #define E_BOOK_BACKEND_DECSYNC_VERSION_NAME "PAS-DB-VERSION" 52 | #define E_BOOK_BACKEND_DECSYNC_VERSION "0.2" 53 | 54 | #define E_BOOK_BACKEND_DECSYNC_REVISION_NAME "PAS-DB-REVISION" 55 | 56 | #define PAS_ID_PREFIX "pas-id-" 57 | 58 | /* We need to specify the folderid in order to properly 59 | * migrate data from the old EBookSqliteDB API. 60 | */ 61 | #define SQLITEDB_FOLDER_ID "folder_id" 62 | #define SQLITE_REVISION_KEY "revision" 63 | 64 | /* Forward Declarations */ 65 | static gboolean book_backend_decsync_refresh_start (EBookBackendDecsync *bf); 66 | static void e_book_backend_decsync_initable_init 67 | (GInitableIface *iface); 68 | 69 | struct _EBookBackendDecsyncPrivate { 70 | gchar *base_directory; 71 | gchar *photo_dirname; 72 | gchar *revision; 73 | gchar *locale; 74 | volatile gint rev_counter; 75 | gboolean revision_guards; 76 | GRWLock lock; 77 | GList *cursors; 78 | 79 | EBookSqlite *sqlitedb; 80 | Decsync decsync; 81 | }; 82 | 83 | G_DEFINE_TYPE_WITH_CODE ( 84 | EBookBackendDecsync, 85 | e_book_backend_decsync, 86 | E_TYPE_BOOK_BACKEND_SYNC, 87 | G_ADD_PRIVATE (EBookBackendDecsync) 88 | G_IMPLEMENT_INTERFACE ( 89 | G_TYPE_INITABLE, 90 | e_book_backend_decsync_initable_init)) 91 | 92 | /**************************************************************** 93 | * File Management helper APIs * 94 | ****************************************************************/ 95 | typedef enum { 96 | GET_PATH_DB_DIR, 97 | GET_PATH_PHOTO_DIR 98 | } GetPathType; 99 | 100 | typedef enum { 101 | STATUS_NORMAL = 0, 102 | STATUS_MODIFIED, 103 | STATUS_ERROR 104 | } PhotoModifiedStatus; 105 | 106 | static gboolean 107 | remove_file (const gchar *filename, 108 | GError **error) 109 | { 110 | if (-1 == g_unlink (filename)) { 111 | if (errno == EACCES || errno == EPERM) { 112 | g_set_error_literal ( 113 | error, E_CLIENT_ERROR, 114 | E_CLIENT_ERROR_PERMISSION_DENIED, 115 | e_client_error_to_string ( 116 | E_CLIENT_ERROR_PERMISSION_DENIED)); 117 | } else { 118 | g_set_error ( 119 | error, E_CLIENT_ERROR, 120 | E_CLIENT_ERROR_OTHER_ERROR, 121 | _("Failed to remove file “%s”: %s"), 122 | filename, g_strerror (errno)); 123 | } 124 | return FALSE; 125 | } 126 | 127 | return TRUE; 128 | } 129 | 130 | static gboolean 131 | create_directory (const gchar *dirname, 132 | GError **error) 133 | { 134 | gint rv; 135 | 136 | rv = g_mkdir_with_parents (dirname, 0700); 137 | if (rv == -1 && errno != EEXIST) { 138 | g_warning ("failed to make directory %s: %s", dirname, g_strerror (errno)); 139 | if (errno == EACCES || errno == EPERM) 140 | g_set_error_literal ( 141 | error, E_CLIENT_ERROR, 142 | E_CLIENT_ERROR_PERMISSION_DENIED, 143 | e_client_error_to_string ( 144 | E_CLIENT_ERROR_PERMISSION_DENIED)); 145 | else 146 | g_set_error ( 147 | error, E_CLIENT_ERROR, 148 | E_CLIENT_ERROR_OTHER_ERROR, 149 | _("Failed to make directory %s: %s"), 150 | dirname, g_strerror (errno)); 151 | return FALSE; 152 | } 153 | return TRUE; 154 | } 155 | 156 | static gchar * 157 | check_remove_uri_for_field (EContact *old_contact, 158 | EContact *new_contact, 159 | EContactField field) 160 | { 161 | EContactPhoto *old_photo = NULL, *new_photo = NULL; 162 | gchar *uri = NULL; 163 | 164 | old_photo = e_contact_get (old_contact, field); 165 | if (!old_photo) 166 | return NULL; 167 | 168 | if (new_contact) { 169 | 170 | new_photo = e_contact_get (new_contact, field); 171 | 172 | if (new_photo == NULL || 173 | g_ascii_strcasecmp (old_photo->data.uri, new_photo->data.uri)) 174 | uri = g_strdup (old_photo->data.uri); 175 | } else { 176 | uri = g_strdup (old_photo->data.uri); 177 | } 178 | 179 | e_contact_photo_free (old_photo); 180 | e_contact_photo_free (new_photo); 181 | 182 | return uri; 183 | } 184 | 185 | static void 186 | maybe_delete_uri (EBookBackendDecsync *bf, 187 | const gchar *uri) 188 | { 189 | GError *error = NULL; 190 | gchar *filename; 191 | 192 | /* A uri that does not give us a filename is certainly not 193 | * a uri that we created for a local file, just skip it */ 194 | if ((filename = g_filename_from_uri (uri, NULL, NULL)) == NULL) 195 | return; 196 | 197 | /* If the file is in our path it belongs to us and we need to delete it. 198 | */ 199 | if (bf->priv->photo_dirname && 200 | !strncmp (bf->priv->photo_dirname, filename, strlen (bf->priv->photo_dirname))) { 201 | 202 | d (g_print ("Deleting uri file: %s\n", filename)); 203 | 204 | /* Deleting uris should not cause the backend to fail to update 205 | * a contact so the best we can do from here is log warnings 206 | * when we fail to unlink a file from the disk. 207 | */ 208 | if (!remove_file (filename, &error)) { 209 | g_warning ("Unable to cleanup photo uri: %s", error->message); 210 | g_error_free (error); 211 | } 212 | } 213 | 214 | g_free (filename); 215 | } 216 | 217 | static void 218 | maybe_delete_unused_uris (EBookBackendDecsync *bf, 219 | EContact *old_contact, 220 | EContact *new_contact) 221 | { 222 | gchar *uri_photo, *uri_logo; 223 | 224 | g_return_if_fail (old_contact != NULL); 225 | 226 | /* If there is no new contact, collect all the uris to delete from old_contact 227 | * 228 | * Otherwise, if any of the photo uri fields have changed in new_contact, then collect the 229 | * old uris for those fields from old_contact to delete 230 | */ 231 | uri_photo = check_remove_uri_for_field (old_contact, new_contact, E_CONTACT_PHOTO); 232 | uri_logo = check_remove_uri_for_field (old_contact, new_contact, E_CONTACT_LOGO); 233 | 234 | if (uri_photo) { 235 | maybe_delete_uri (bf, uri_photo); 236 | g_free (uri_photo); 237 | } 238 | 239 | if (uri_logo) { 240 | maybe_delete_uri (bf, uri_logo); 241 | g_free (uri_logo); 242 | } 243 | } 244 | 245 | static gchar * 246 | e_book_backend_decsync_extract_path_from_source (ESourceRegistry *registry, 247 | ESource *source, 248 | GetPathType path_type) 249 | { 250 | ESource *builtin_source; 251 | const gchar *user_data_dir; 252 | const gchar *uid; 253 | gchar *filename = NULL; 254 | 255 | uid = e_source_get_uid (source); 256 | g_return_val_if_fail (uid != NULL, NULL); 257 | 258 | user_data_dir = e_get_user_data_dir (); 259 | 260 | builtin_source = e_source_registry_ref_builtin_address_book (registry); 261 | 262 | /* XXX Backward-compatibility hack: 263 | * 264 | * The special built-in "Personal" data source UIDs are now named 265 | * "system-$COMPONENT" but since the data directories are already 266 | * split out by component, we'll continue to use the old "system" 267 | * directories for these particular data sources. */ 268 | if (e_source_equal (source, builtin_source)) 269 | uid = "system"; 270 | 271 | switch (path_type) { 272 | case GET_PATH_DB_DIR: 273 | filename = g_build_filename ( 274 | user_data_dir, "addressbook", uid, NULL); 275 | break; 276 | case GET_PATH_PHOTO_DIR: 277 | filename = g_build_filename ( 278 | user_data_dir, "addressbook", uid, "photos", NULL); 279 | break; 280 | default: 281 | g_warn_if_reached (); 282 | } 283 | 284 | g_object_unref (builtin_source); 285 | 286 | return filename; 287 | } 288 | 289 | static gchar * 290 | safe_name_for_photo (EBookBackendDecsync *bf, 291 | EContact *contact, 292 | EContactPhoto *photo, 293 | EContactField field) 294 | { 295 | gchar *fullname = NULL, *name, *str; 296 | gchar *suffix = NULL; 297 | gint i = 0; 298 | 299 | g_return_val_if_fail (photo->type == E_CONTACT_PHOTO_TYPE_INLINED, NULL); 300 | 301 | /* Get a suitable filename extension */ 302 | if (photo->data.inlined.mime_type != NULL && 303 | photo->data.inlined.mime_type[0] != '\0' && 304 | g_ascii_strcasecmp (photo->data.inlined.mime_type, "image/X-EVOLUTION-UNKNOWN") != 0) { 305 | suffix = g_uri_escape_string ( 306 | photo->data.inlined.mime_type, 307 | NULL, TRUE); 308 | } else { 309 | gchar *mime_type = NULL; 310 | gchar *content_type = NULL; 311 | 312 | content_type = g_content_type_guess ( 313 | NULL, 314 | photo->data.inlined.data, 315 | photo->data.inlined.length, 316 | NULL); 317 | 318 | if (content_type) 319 | mime_type = g_content_type_get_mime_type (content_type); 320 | 321 | if (mime_type) 322 | suffix = g_uri_escape_string (mime_type, NULL, TRUE); 323 | else 324 | suffix = g_strdup ("data"); 325 | 326 | g_free (mime_type); 327 | g_free (content_type); 328 | } 329 | 330 | /* Replace any percent in the text with a dash, to avoid escaping issues in URI-s */ 331 | str = suffix; 332 | while (str = strchr (str, '%'), str) { 333 | *str = '-'; 334 | } 335 | 336 | /* Create a filename based on the uid/field */ 337 | name = g_strconcat ( 338 | e_contact_get_const (contact, E_CONTACT_UID), "_", 339 | e_contact_field_name (field), NULL); 340 | name = g_strdelimit (name, NULL, '_'); 341 | 342 | /* Replace any percent in the text with a dash, to avoid escaping issues in URI-s */ 343 | str = name; 344 | while (str = strchr (str, '%'), str) { 345 | *str = '-'; 346 | } 347 | 348 | do { 349 | g_free (fullname); 350 | 351 | str = e_filename_mkdir_encoded (bf->priv->photo_dirname, name, NULL, i); 352 | fullname = g_strdup_printf ("%s.%s", str, suffix); 353 | g_free (str); 354 | 355 | i++; 356 | } while (g_file_test (fullname, G_FILE_TEST_EXISTS)); 357 | 358 | g_free (name); 359 | g_free (suffix); 360 | 361 | return fullname; 362 | } 363 | 364 | static gchar * 365 | hard_link_photo (EBookBackendDecsync *bf, 366 | EContact *contact, 367 | EContactField field, 368 | const gchar *src_filename, 369 | GError **error) 370 | { 371 | gchar *fullname = NULL, *name, *str; 372 | gint i = 0, ret; 373 | const gchar *suffix; 374 | 375 | /* Copy over the file suffix */ 376 | suffix = strrchr (src_filename, '.'); 377 | if (suffix) 378 | suffix++; 379 | 380 | if (!suffix) 381 | suffix = "data"; 382 | 383 | /* Create a filename based on uid/field */ 384 | name = g_strconcat ( 385 | e_contact_get_const (contact, E_CONTACT_UID), "_", 386 | e_contact_field_name (field), NULL); 387 | name = g_strdelimit (name, NULL, '_'); 388 | 389 | do { 390 | g_free (fullname); 391 | 392 | str = e_filename_mkdir_encoded (bf->priv->photo_dirname, name, NULL, i); 393 | fullname = g_strdup_printf ("%s.%s", str, suffix); 394 | g_free (str); 395 | 396 | i++; 397 | 398 | #ifdef G_OS_WIN32 399 | { 400 | GFile *src_file = g_file_new_for_path (src_filename); 401 | GFile *dst_file = g_file_new_for_path (fullname); 402 | GError *copy_error = NULL; 403 | 404 | g_file_copy (src_file, dst_file, G_FILE_COPY_NONE, 405 | NULL, NULL, NULL, ©_error); 406 | 407 | if (copy_error != NULL) { 408 | g_error_free (copy_error); 409 | ret = -1; 410 | } else 411 | ret = 0; 412 | 413 | g_object_unref (src_file); 414 | g_object_unref (dst_file); 415 | } 416 | #else 417 | ret = link (src_filename, fullname); 418 | #endif 419 | 420 | } while (ret < 0 && errno == EEXIST); 421 | 422 | if (ret < 0) { 423 | if (errno == EACCES || errno == EPERM) { 424 | g_set_error_literal ( 425 | error, E_CLIENT_ERROR, 426 | E_CLIENT_ERROR_PERMISSION_DENIED, 427 | e_client_error_to_string ( 428 | E_CLIENT_ERROR_PERMISSION_DENIED)); 429 | } else { 430 | g_set_error ( 431 | error, E_CLIENT_ERROR, 432 | E_CLIENT_ERROR_OTHER_ERROR, 433 | _("Failed to create hardlink for resource “%s”: %s"), 434 | src_filename, g_strerror (errno)); 435 | } 436 | g_free (fullname); 437 | fullname = NULL; 438 | } 439 | 440 | g_free (name); 441 | 442 | return fullname; 443 | } 444 | 445 | static gboolean 446 | is_backend_owned_uri (EBookBackendDecsync *bf, 447 | const gchar *uri) 448 | { 449 | gchar *filename; 450 | gchar *dirname; 451 | gboolean owned_uri; 452 | 453 | /* Errors converting from uri definitily indicate it was 454 | * not our uri to begin with, so just disregard this error. */ 455 | filename = g_filename_from_uri (uri, NULL, NULL); 456 | if (!filename) 457 | return FALSE; 458 | 459 | dirname = g_path_get_dirname (filename); 460 | 461 | owned_uri = bf->priv->photo_dirname && (strcmp (dirname, bf->priv->photo_dirname) == 0); 462 | 463 | g_free (filename); 464 | g_free (dirname); 465 | 466 | return owned_uri; 467 | } 468 | 469 | static PhotoModifiedStatus 470 | maybe_transform_vcard_field_for_photo (EBookBackendDecsync *bf, 471 | EContact *old_contact, 472 | EContact *contact, 473 | EContactField field, 474 | GError **error) 475 | { 476 | PhotoModifiedStatus status = STATUS_NORMAL; 477 | EContactPhoto *photo; 478 | 479 | if (field != E_CONTACT_PHOTO && field != E_CONTACT_LOGO) 480 | return status; 481 | 482 | photo = e_contact_get (contact, field); 483 | if (!photo) 484 | return status; 485 | 486 | if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) { 487 | EContactPhoto *new_photo; 488 | gchar *new_photo_path; 489 | gchar *uri; 490 | 491 | /* Create a unique filename with an extension (hopefully) based on the mime type */ 492 | new_photo_path = safe_name_for_photo (bf, contact, photo, field); 493 | 494 | if ((uri = 495 | g_filename_to_uri (new_photo_path, NULL, error)) == NULL) { 496 | 497 | status = STATUS_ERROR; 498 | } else if (!g_file_set_contents (new_photo_path, 499 | (const gchar *) photo->data.inlined.data, 500 | photo->data.inlined.length, 501 | error)) { 502 | 503 | status = STATUS_ERROR; 504 | } else { 505 | new_photo = e_contact_photo_new (); 506 | new_photo->type = E_CONTACT_PHOTO_TYPE_URI; 507 | new_photo->data.uri = g_strdup (uri); 508 | 509 | e_contact_set (contact, field, new_photo); 510 | 511 | d (g_print ("Backend modified incomming binary blob to be %s:\n", uri)); 512 | 513 | status = STATUS_MODIFIED; 514 | 515 | e_contact_photo_free (new_photo); 516 | } 517 | 518 | g_free (uri); 519 | g_free (new_photo_path); 520 | 521 | } else { /* E_CONTACT_PHOTO_TYPE_URI */ 522 | const gchar *uid; 523 | EContactPhoto *old_photo = NULL, *new_photo; 524 | 525 | /* First determine that the new contact uri points to our 'photos' directory, 526 | * if not then we do nothing 527 | */ 528 | if (!is_backend_owned_uri (bf, photo->data.uri)) 529 | goto done; 530 | 531 | /* Now check if the uri is changed from the BDB copy 532 | */ 533 | uid = e_contact_get_const (contact, E_CONTACT_UID); 534 | if (uid == NULL) { 535 | g_set_error_literal ( 536 | error, E_CLIENT_ERROR, 537 | E_CLIENT_ERROR_OTHER_ERROR, 538 | _("No UID in the contact")); 539 | status = STATUS_ERROR; 540 | goto done; 541 | } 542 | 543 | if (old_contact) 544 | old_photo = e_contact_get (old_contact, field); 545 | 546 | /* Unless we are receiving the same uri that we already have 547 | * stored in the BDB... */ 548 | if (!old_photo || old_photo->type == E_CONTACT_PHOTO_TYPE_INLINED || 549 | g_ascii_strcasecmp (old_photo->data.uri, photo->data.uri) != 0) { 550 | gchar *filename; 551 | gchar *new_filename; 552 | gchar *new_uri = NULL; 553 | 554 | /* ... Assume that the incomming uri belongs to another contact 555 | * still in the BDB. Lets go ahead and create a hard link to the 556 | * photo file and create a new name for the incomming uri, and 557 | * use that in the incomming contact to save in place. 558 | * 559 | * This piece of code is here to ensure there are no problems if 560 | * the libebook user decides to cross-reference and start "sharing" 561 | * uris that we've previously stored in the photo directory. 562 | * 563 | * We use the hard-link here to off-load the necessary ref-counting 564 | * logic to the file-system. 565 | */ 566 | filename = g_filename_from_uri (photo->data.uri, NULL, NULL); 567 | g_return_val_if_fail (filename, STATUS_NORMAL); /* we already checked this with 'is_backend_owned_uri ()' */ 568 | 569 | new_filename = hard_link_photo (bf, contact, field, filename, error); 570 | 571 | if (!new_filename) 572 | status = STATUS_ERROR; 573 | else if ((new_uri = g_filename_to_uri (new_filename, NULL, error)) == NULL) { 574 | /* If we fail here... we need to clean up the hardlink we just created */ 575 | GError *local_err = NULL; 576 | if (!remove_file (new_filename, &local_err)) { 577 | g_warning ("Unable to cleanup photo uri: %s", local_err->message); 578 | g_error_free (local_err); 579 | } 580 | status = STATUS_ERROR; 581 | } else { 582 | 583 | new_photo = e_contact_photo_new (); 584 | new_photo->type = E_CONTACT_PHOTO_TYPE_URI; 585 | new_photo->data.uri = new_uri; 586 | 587 | e_contact_set (contact, field, new_photo); 588 | 589 | d (g_print ("Backend modified incomming shared uri to be %s:\n", new_uri)); 590 | 591 | e_contact_photo_free (new_photo); 592 | status = STATUS_MODIFIED; 593 | } 594 | g_free (new_filename); 595 | g_free (filename); 596 | } 597 | 598 | if (old_photo) 599 | e_contact_photo_free (old_photo); 600 | 601 | } 602 | 603 | done: 604 | e_contact_photo_free (photo); 605 | 606 | return status; 607 | } 608 | 609 | /* 610 | * When a contact is added or modified we receive a vCard, 611 | * this function checks if we've received inline data 612 | * and replaces it with a uri notation. 613 | * 614 | * If this function modifies 'contact' then it will 615 | * return the 'modified' status and 'vcard_ret' (if specified) 616 | * will be set to a newly allocated vcard string. 617 | */ 618 | static PhotoModifiedStatus 619 | maybe_transform_vcard_for_photo (EBookBackendDecsync *bf, 620 | EContact *old_contact, 621 | EContact *contact, 622 | GError **error) 623 | { 624 | PhotoModifiedStatus status; 625 | gboolean modified = FALSE; 626 | 627 | status = maybe_transform_vcard_field_for_photo ( 628 | bf, old_contact, contact, 629 | E_CONTACT_PHOTO, error); 630 | modified = (status == STATUS_MODIFIED); 631 | 632 | if (status != STATUS_ERROR) { 633 | status = maybe_transform_vcard_field_for_photo ( 634 | bf, old_contact, contact, 635 | E_CONTACT_LOGO, error); 636 | modified = modified || (status == STATUS_MODIFIED); 637 | } 638 | 639 | if (status != STATUS_ERROR && modified) 640 | status = STATUS_MODIFIED; 641 | 642 | return status; 643 | } 644 | 645 | /**************************************************************** 646 | * Global Revisioning Tools * 647 | ****************************************************************/ 648 | static gchar * 649 | e_book_backend_decsync_create_unique_id (void) 650 | { 651 | /* use a 32 counter and the 32 bit timestamp to make an id. 652 | * it's doubtful 2^32 id's will be created in a second, so we 653 | * should be okay. */ 654 | static guint c = 0; 655 | return g_strdup_printf (PAS_ID_PREFIX "%08lX%08X", (glong) time (NULL), c++); 656 | } 657 | 658 | static gchar * 659 | e_book_backend_decsync_new_revision (EBookBackendDecsync *bf, 660 | gboolean with_counter) 661 | { 662 | gchar time_string[100] = {0}; 663 | const struct tm *tm = NULL; 664 | time_t t; 665 | 666 | t = time (NULL); 667 | tm = gmtime (&t); 668 | if (tm) { 669 | struct tm ttm = *tm; 670 | 671 | if (!with_counter && bf->priv->revision_guards) { 672 | gint value = g_atomic_int_add (&bf->priv->rev_counter, 1); 673 | 674 | /* Artificial time, which encodes the revision counter */ 675 | ttm.tm_sec = value % 60; 676 | ttm.tm_min = (value / 60 ) % 60; 677 | ttm.tm_hour = (value / 3600) % 24; 678 | } 679 | 680 | strftime (time_string, 100, "%Y-%m-%dT%H:%M:%SZ", &ttm); 681 | } 682 | 683 | if (with_counter) 684 | return g_strdup_printf ("%s(%d)", time_string, g_atomic_int_add (&bf->priv->rev_counter, 1)); 685 | 686 | return g_strdup (time_string); 687 | } 688 | 689 | /* For now just bump the revision and set it in the DB every 690 | * time the revision bumps, this is the safest approach and 691 | * its unclear so far if bumping the revision string for 692 | * every DB modification is going to really be an overhead. 693 | */ 694 | static gboolean 695 | e_book_backend_decsync_bump_revision (EBookBackendDecsync *bf, 696 | GError **error) 697 | { 698 | GError *local_error = NULL; 699 | gchar *new_revision; 700 | gboolean success; 701 | 702 | new_revision = e_book_backend_decsync_new_revision (bf, TRUE); 703 | success = e_book_sqlite_set_key_value ( 704 | bf->priv->sqlitedb, 705 | SQLITE_REVISION_KEY, 706 | new_revision, 707 | &local_error); 708 | 709 | if (success) { 710 | g_free (bf->priv->revision); 711 | bf->priv->revision = new_revision; 712 | 713 | e_book_backend_notify_property_changed (E_BOOK_BACKEND (bf), 714 | E_BOOK_BACKEND_PROPERTY_REVISION, 715 | bf->priv->revision); 716 | } else { 717 | g_free (new_revision); 718 | g_warning ( 719 | G_STRLOC ": Error setting database revision: %s", 720 | local_error->message); 721 | g_propagate_error (error, local_error); 722 | } 723 | 724 | return success; 725 | } 726 | 727 | static void 728 | e_book_backend_decsync_load_revision (EBookBackendDecsync *bf) 729 | { 730 | GError *error = NULL; 731 | 732 | if (!e_book_sqlite_get_key_value (bf->priv->sqlitedb, 733 | SQLITE_REVISION_KEY, 734 | &bf->priv->revision, 735 | &error)) { 736 | g_warning ( 737 | G_STRLOC ": Error loading database revision: %s", 738 | error ? error->message : "Unknown error"); 739 | g_clear_error (&error); 740 | } else if (bf->priv->revision == NULL) { 741 | e_book_backend_decsync_bump_revision (bf, NULL); 742 | } 743 | } 744 | 745 | static void 746 | e_book_backend_decsync_load_locale (EBookBackendDecsync *bf) 747 | { 748 | GError *error = NULL; 749 | 750 | if (!e_book_sqlite_get_locale (bf->priv->sqlitedb, 751 | &bf->priv->locale, 752 | &error)) { 753 | g_warning ( 754 | G_STRLOC ": Error loading database locale setting: %s", 755 | error ? error->message : "Unknown error"); 756 | g_clear_error (&error); 757 | } 758 | } 759 | 760 | static void 761 | set_revision (EBookBackendDecsync *bf, 762 | EContact *contact) 763 | { 764 | gchar *rev; 765 | 766 | rev = e_book_backend_decsync_new_revision (bf, FALSE); 767 | e_contact_set (contact, E_CONTACT_REV, rev); 768 | g_free (rev); 769 | } 770 | 771 | /**************************************************************** 772 | * Dealing with cursor updates * 773 | ****************************************************************/ 774 | static void 775 | cursors_contact_added (EBookBackendDecsync *bf, 776 | EContact *contact) 777 | { 778 | GList *l; 779 | 780 | for (l = bf->priv->cursors; l; l = l->next) { 781 | EDataBookCursor *cursor = l->data; 782 | 783 | e_data_book_cursor_contact_added (cursor, contact); 784 | } 785 | } 786 | 787 | static void 788 | cursors_contact_removed (EBookBackendDecsync *bf, 789 | EContact *contact) 790 | { 791 | GList *l; 792 | 793 | for (l = bf->priv->cursors; l; l = l->next) { 794 | EDataBookCursor *cursor = l->data; 795 | 796 | e_data_book_cursor_contact_removed (cursor, contact); 797 | } 798 | } 799 | 800 | /**************************************************************** 801 | * Main Backend Implementation * 802 | ****************************************************************/ 803 | 804 | /** 805 | * This method will return TRUE if all the contacts were properly created. 806 | * If at least one contact fails, the method will return FALSE, all 807 | * changes will be reverted (the @contacts list will stay empty) and 808 | * @perror will be set. 809 | */ 810 | static gboolean 811 | do_create (EBookBackendDecsync *bf, 812 | const gchar * const *vcards, 813 | const gchar * const *uids, 814 | GSList **out_contacts, 815 | GCancellable *cancellable, 816 | GError **error, 817 | gboolean update_decsync) 818 | { 819 | PhotoModifiedStatus status = STATUS_NORMAL; 820 | guint ii, length; 821 | GError *local_error = NULL; 822 | const gchar *path[2]; 823 | JsonNode *key_node, *value_node; 824 | gchar *key_string, *value_string; 825 | 826 | length = g_strv_length ((gchar **) vcards); 827 | 828 | for (ii = 0; ii < length; ii++) { 829 | gchar *id; 830 | const gchar *rev; 831 | EContact *contact; 832 | 833 | if (uids != NULL && uids[ii] != NULL) 834 | contact = e_contact_new_from_vcard_with_uid (vcards[ii], uids[ii]); 835 | else 836 | contact = e_contact_new_from_vcard (vcards[ii]); 837 | 838 | /* Preserve original UID, create a unique UID if needed */ 839 | if (e_contact_get_const (contact, E_CONTACT_UID) == NULL) { 840 | id = e_book_backend_decsync_create_unique_id (); 841 | e_contact_set (contact, E_CONTACT_UID, id); 842 | g_free (id); 843 | } 844 | 845 | if (update_decsync) { 846 | id = e_contact_get (contact, E_CONTACT_UID); 847 | 848 | path[0] = "resources"; 849 | path[1] = id; 850 | key_node = json_node_new (JSON_NODE_NULL); 851 | key_string = json_to_string (key_node, FALSE); 852 | value_node = json_node_new (JSON_NODE_VALUE); 853 | json_node_set_string (value_node, vcards[ii]); 854 | value_string = json_to_string (value_node, FALSE); 855 | decsync_set_entry(bf->priv->decsync, path, 2, key_string, value_string); 856 | json_node_free (key_node); 857 | g_free (key_string); 858 | json_node_free (value_node); 859 | g_free (value_string); 860 | 861 | g_free (id); 862 | } 863 | 864 | rev = e_contact_get_const (contact, E_CONTACT_REV); 865 | if (!(rev && *rev)) 866 | set_revision (bf, contact); 867 | 868 | status = maybe_transform_vcard_for_photo (bf, NULL, contact, error); 869 | 870 | if (status != STATUS_ERROR) { 871 | 872 | /* Contact was added successfully. */ 873 | *out_contacts = g_slist_prepend (*out_contacts, contact); 874 | } else { 875 | /* Contact could not be transformed */ 876 | g_warning ( 877 | G_STRLOC ": Error transforming vcard with image data %s", 878 | (error && *error) ? (*error)->message : 879 | "Unknown error transforming vcard"); 880 | g_object_unref (contact); 881 | 882 | /* Abort as soon as an error occurs */ 883 | break; 884 | } 885 | } 886 | 887 | if (status != STATUS_ERROR) { 888 | GSList *link; 889 | 890 | if (!e_book_sqlite_add_contacts (bf->priv->sqlitedb, 891 | *out_contacts, NULL, FALSE, 892 | cancellable, 893 | &local_error)) { 894 | 895 | if (g_error_matches (local_error, 896 | E_BOOK_SQLITE_ERROR, 897 | E_BOOK_SQLITE_ERROR_CONSTRAINT)) { 898 | g_set_error ( 899 | error, E_BOOK_CLIENT_ERROR, 900 | E_BOOK_CLIENT_ERROR_CONTACT_ID_ALREADY_EXISTS, 901 | _("Conflicting UIDs found in added contacts")); 902 | g_clear_error (&local_error); 903 | } else { 904 | g_warning ("Failed to add contacts: %s", local_error->message); 905 | g_propagate_error (error, local_error); 906 | } 907 | 908 | status = STATUS_ERROR; 909 | } 910 | 911 | /* After adding any contacts, notify any cursors that the new contacts are added */ 912 | for (link = *out_contacts; link; link = g_slist_next (link)) { 913 | cursors_contact_added (bf, link->data); 914 | } 915 | } 916 | 917 | return (status != STATUS_ERROR); 918 | } 919 | 920 | typedef struct { 921 | EBookBackendDecsync *bf; 922 | GThread *thread; 923 | EFlag *running; 924 | } DecsyncBackendSearchClosure; 925 | 926 | static void 927 | closure_destroy (DecsyncBackendSearchClosure *closure) 928 | { 929 | d (printf ("destroying search closure\n")); 930 | e_flag_free (closure->running); 931 | if (closure->thread) 932 | g_thread_unref (closure->thread); 933 | g_free (closure); 934 | } 935 | 936 | static DecsyncBackendSearchClosure * 937 | init_closure (EDataBookView *book_view, 938 | EBookBackendDecsync *bf) 939 | { 940 | DecsyncBackendSearchClosure *closure = g_new (DecsyncBackendSearchClosure, 1); 941 | 942 | closure->bf = bf; 943 | closure->thread = NULL; 944 | closure->running = e_flag_new (); 945 | 946 | g_object_set_data_full ( 947 | G_OBJECT (book_view), 948 | "EBookBackendDecsync.BookView::closure", 949 | closure, (GDestroyNotify) closure_destroy); 950 | 951 | return closure; 952 | } 953 | 954 | static DecsyncBackendSearchClosure * 955 | get_closure (EDataBookView *book_view) 956 | { 957 | return g_object_get_data ( 958 | G_OBJECT (book_view), 959 | "EBookBackendDecsync.BookView::closure"); 960 | } 961 | 962 | static void 963 | notify_update_vcard (EDataBookView *book_view, 964 | gboolean prefiltered, 965 | const gchar *id, 966 | const gchar *vcard) 967 | { 968 | if (prefiltered) 969 | e_data_book_view_notify_update_prefiltered_vcard (book_view, id, vcard); 970 | else 971 | e_data_book_view_notify_update_vcard (book_view, id, vcard); 972 | } 973 | 974 | static gboolean 975 | uid_rev_fields (GHashTable *fields_of_interest) 976 | { 977 | GHashTableIter iter; 978 | gpointer key, value; 979 | 980 | if (!fields_of_interest || g_hash_table_size (fields_of_interest) > 2) 981 | return FALSE; 982 | 983 | g_hash_table_iter_init (&iter, fields_of_interest); 984 | while (g_hash_table_iter_next (&iter, &key, &value)) { 985 | const gchar *field_name = key; 986 | EContactField field = e_contact_field_id (field_name); 987 | 988 | if (field != E_CONTACT_UID && 989 | field != E_CONTACT_REV) 990 | return FALSE; 991 | } 992 | 993 | return TRUE; 994 | } 995 | 996 | static gpointer 997 | book_view_thread (gpointer user_data) 998 | { 999 | EDataBookView *book_view = user_data; 1000 | DecsyncBackendSearchClosure *closure; 1001 | EBookBackendDecsync *bf; 1002 | EBookBackendSExp *sexp; 1003 | const gchar *query; 1004 | GSList *summary_list = NULL, *l; 1005 | GHashTable *fields_of_interest; 1006 | GError *local_error = NULL; 1007 | gboolean meta_contact, success; 1008 | 1009 | g_return_val_if_fail (E_IS_DATA_BOOK_VIEW (book_view), NULL); 1010 | 1011 | closure = get_closure (book_view); 1012 | if (!closure) { 1013 | g_warning (G_STRLOC ": NULL closure in book view thread"); 1014 | return NULL; 1015 | } 1016 | bf = closure->bf; 1017 | 1018 | d (printf ("starting initial population of book view\n")); 1019 | 1020 | /* ref the book view because it'll be removed and unrefed 1021 | * when/if it's stopped */ 1022 | g_object_ref (book_view); 1023 | 1024 | sexp = e_data_book_view_get_sexp (book_view); 1025 | query = e_book_backend_sexp_text (sexp); 1026 | 1027 | fields_of_interest = e_data_book_view_get_fields_of_interest (book_view); 1028 | meta_contact = uid_rev_fields (fields_of_interest); 1029 | 1030 | if (query && !strcmp (query, "(contains \"x-evolution-any-field\" \"\")")) { 1031 | e_data_book_view_notify_progress (book_view, -1, _("Loading...")); 1032 | } else { 1033 | e_data_book_view_notify_progress (book_view, -1, _("Searching...")); 1034 | } 1035 | 1036 | d (printf ("signalling parent thread\n")); 1037 | e_flag_set (closure->running); 1038 | 1039 | g_rw_lock_reader_lock (&(bf->priv->lock)); 1040 | success = e_book_sqlite_search ( 1041 | bf->priv->sqlitedb, 1042 | query, 1043 | meta_contact, 1044 | &summary_list, 1045 | NULL, /* GCancellable */ 1046 | &local_error); 1047 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 1048 | 1049 | if (!success) { 1050 | g_warning (G_STRLOC ": Failed to query initial contacts: %s", local_error->message); 1051 | g_error_free (local_error); 1052 | e_data_book_view_notify_complete ( 1053 | book_view, 1054 | g_error_new_literal ( 1055 | E_CLIENT_ERROR, 1056 | E_CLIENT_ERROR_NOT_OPENED, 1057 | e_client_error_to_string ( 1058 | E_CLIENT_ERROR_NOT_OPENED))); 1059 | g_object_unref (book_view); 1060 | return NULL; 1061 | } 1062 | 1063 | for (l = summary_list; l; l = l->next) { 1064 | EbSqlSearchData *data = l->data; 1065 | gchar *vcard = NULL; 1066 | 1067 | vcard = data->vcard; 1068 | data->vcard = NULL; 1069 | 1070 | notify_update_vcard (book_view, TRUE, data->uid, vcard); 1071 | g_free (vcard); 1072 | } 1073 | 1074 | g_slist_foreach (summary_list, (GFunc) e_book_sqlite_search_data_free, NULL); 1075 | g_slist_free (summary_list); 1076 | 1077 | if (e_flag_is_set (closure->running)) 1078 | e_data_book_view_notify_complete (book_view, NULL /* Success */); 1079 | 1080 | g_object_unref (book_view); 1081 | 1082 | d (printf ("finished population of book view\n")); 1083 | 1084 | return NULL; 1085 | } 1086 | 1087 | static void 1088 | book_backend_decsync_dispose (GObject *object) 1089 | { 1090 | EBookBackendDecsync *bf; 1091 | 1092 | bf = E_BOOK_BACKEND_DECSYNC (object); 1093 | 1094 | g_rw_lock_writer_lock (&(bf->priv->lock)); 1095 | 1096 | if (bf->priv->cursors) { 1097 | g_list_free_full (bf->priv->cursors, g_object_unref); 1098 | bf->priv->cursors = NULL; 1099 | } 1100 | 1101 | g_clear_object (&bf->priv->sqlitedb); 1102 | 1103 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1104 | 1105 | G_OBJECT_CLASS (e_book_backend_decsync_parent_class)->dispose (object); 1106 | } 1107 | 1108 | static void 1109 | book_backend_decsync_finalize (GObject *object) 1110 | { 1111 | EBookBackendDecsyncPrivate *priv; 1112 | 1113 | priv = E_BOOK_BACKEND_DECSYNC (object)->priv; 1114 | 1115 | g_free (priv->photo_dirname); 1116 | g_free (priv->revision); 1117 | g_free (priv->locale); 1118 | g_free (priv->base_directory); 1119 | g_rw_lock_clear (&(priv->lock)); 1120 | 1121 | /* Chain up to parent's finalize() method. */ 1122 | G_OBJECT_CLASS (e_book_backend_decsync_parent_class)->finalize (object); 1123 | } 1124 | 1125 | static gchar * 1126 | book_backend_decsync_get_backend_property (EBookBackend *backend, 1127 | const gchar *prop_name) 1128 | { 1129 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1130 | 1131 | g_return_val_if_fail (prop_name != NULL, NULL); 1132 | 1133 | if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) { 1134 | return g_strdup ("local,do-initial-query,bulk-adds,bulk-modifies,bulk-removes,contact-lists,refresh-supported"); 1135 | 1136 | } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) { 1137 | return g_strdup (e_contact_field_name (E_CONTACT_FILE_AS)); 1138 | 1139 | } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) { 1140 | GString *fields; 1141 | gint ii; 1142 | 1143 | fields = g_string_sized_new (1024); 1144 | 1145 | /* XXX we need a way to say "we support everything", since the 1146 | * file backend does */ 1147 | for (ii = 1; ii < E_CONTACT_FIELD_LAST; ii++) { 1148 | if (fields->len > 0) 1149 | g_string_append_c (fields, ','); 1150 | g_string_append (fields, e_contact_field_name (ii)); 1151 | } 1152 | 1153 | return g_string_free (fields, FALSE); 1154 | 1155 | } else if (g_str_equal (prop_name, E_BOOK_BACKEND_PROPERTY_REVISION)) { 1156 | gchar *prop_value; 1157 | 1158 | g_rw_lock_reader_lock (&(bf->priv->lock)); 1159 | prop_value = g_strdup (bf->priv->revision); 1160 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 1161 | 1162 | return prop_value; 1163 | } 1164 | 1165 | /* Chain up to parent's method. */ 1166 | return E_BOOK_BACKEND_CLASS (e_book_backend_decsync_parent_class)->impl_get_backend_property (backend, prop_name); 1167 | } 1168 | 1169 | static gboolean 1170 | book_backend_decsync_open_sync (EBookBackendSync *backend, 1171 | GCancellable *cancellable, 1172 | GError **error) 1173 | { 1174 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1175 | ESource *source; 1176 | ESourceRevisionGuards *guards; 1177 | 1178 | source = e_backend_get_source (E_BACKEND (backend)); 1179 | 1180 | /* Local source is always connected. */ 1181 | e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED); 1182 | 1183 | g_type_ensure (E_TYPE_SOURCE_REVISION_GUARDS); 1184 | guards = e_source_get_extension (source, E_SOURCE_EXTENSION_REVISION_GUARDS); 1185 | 1186 | bf->priv->revision_guards = e_source_revision_guards_get_enabled (guards); 1187 | 1188 | g_rw_lock_writer_lock (&(bf->priv->lock)); 1189 | if (!bf->priv->revision) { 1190 | e_book_backend_decsync_load_revision (bf); 1191 | e_book_backend_notify_property_changed ( 1192 | E_BOOK_BACKEND (backend), 1193 | E_BOOK_BACKEND_PROPERTY_REVISION, 1194 | bf->priv->revision); 1195 | } 1196 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1197 | 1198 | e_backend_set_online (E_BACKEND (backend), TRUE); 1199 | e_book_backend_set_writable (E_BOOK_BACKEND (backend), TRUE); 1200 | 1201 | g_idle_add ((GSourceFunc) book_backend_decsync_refresh_start, bf); 1202 | 1203 | return TRUE; 1204 | } 1205 | 1206 | static gboolean 1207 | book_backend_decsync_create_contacts_sync_with_decsync (EBookBackendSync *backend, 1208 | const gchar * const *vcards, 1209 | const gchar * const *uids, 1210 | guint32 opflags, 1211 | GSList **out_contacts, 1212 | GCancellable *cancellable, 1213 | GError **error, 1214 | gboolean update_decsync) 1215 | { 1216 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1217 | gboolean success = FALSE; 1218 | 1219 | g_return_val_if_fail (out_contacts != NULL, FALSE); 1220 | 1221 | *out_contacts = NULL; 1222 | 1223 | g_rw_lock_writer_lock (&(bf->priv->lock)); 1224 | if (!e_book_sqlite_lock (bf->priv->sqlitedb, 1225 | EBSQL_LOCK_WRITE, 1226 | cancellable, error)) { 1227 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1228 | return FALSE; 1229 | } 1230 | 1231 | success = do_create (bf, vcards, uids, out_contacts, cancellable, error, update_decsync); 1232 | 1233 | if (success) { 1234 | *out_contacts = g_slist_reverse (*out_contacts); 1235 | } else { 1236 | g_slist_free_full (*out_contacts, g_object_unref); 1237 | *out_contacts = NULL; 1238 | } 1239 | 1240 | if (success) 1241 | success = e_book_backend_decsync_bump_revision (bf, error); 1242 | 1243 | if (success) 1244 | success = e_book_sqlite_unlock ( 1245 | bf->priv->sqlitedb, 1246 | EBSQL_UNLOCK_COMMIT, 1247 | error); 1248 | else { 1249 | GError *local_error = NULL; 1250 | 1251 | /* Rollback transaction */ 1252 | e_book_sqlite_unlock ( 1253 | bf->priv->sqlitedb, 1254 | EBSQL_UNLOCK_ROLLBACK, 1255 | &local_error); 1256 | 1257 | if (local_error != NULL) { 1258 | g_warning ( 1259 | "Failed to rollback transaction " 1260 | "after failing to add contacts: %s", 1261 | local_error->message); 1262 | g_clear_error (&local_error); 1263 | } 1264 | } 1265 | 1266 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1267 | 1268 | return success; 1269 | } 1270 | 1271 | static gboolean 1272 | book_backend_decsync_create_contacts_sync (EBookBackendSync *backend, 1273 | const gchar * const *vcards, 1274 | guint32 opflags, 1275 | GSList **out_contacts, 1276 | GCancellable *cancellable, 1277 | GError **error) 1278 | { 1279 | return book_backend_decsync_create_contacts_sync_with_decsync (backend, vcards, NULL, opflags, out_contacts, cancellable, error, TRUE); 1280 | } 1281 | 1282 | static gboolean 1283 | book_backend_decsync_modify_contacts_sync_with_decsync (EBookBackendSync *backend, 1284 | const gchar * const *vcards, 1285 | const gchar * const *uids, 1286 | guint32 opflags, 1287 | GSList **out_contacts, 1288 | GCancellable *cancellable, 1289 | GError **error, 1290 | gboolean update_decsync) 1291 | { 1292 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1293 | GSList *ids = NULL; 1294 | GError *local_error = NULL; 1295 | PhotoModifiedStatus status = STATUS_NORMAL; 1296 | GSList *old_contacts = NULL; 1297 | guint ii, length; 1298 | const gchar *path[2]; 1299 | JsonNode *key_node, *value_node; 1300 | gchar *key_string, *value_string; 1301 | 1302 | length = g_strv_length ((gchar **) vcards); 1303 | 1304 | g_rw_lock_writer_lock (&(bf->priv->lock)); 1305 | 1306 | if (!e_book_sqlite_lock (bf->priv->sqlitedb, EBSQL_LOCK_WRITE, cancellable, error)) { 1307 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1308 | return FALSE; 1309 | } 1310 | 1311 | for (ii = 0; ii < length && status != STATUS_ERROR; ii++) { 1312 | gchar *id; 1313 | EContact *mod_contact, *old_contact = NULL; 1314 | const gchar *mod_contact_rev, *old_contact_rev; 1315 | 1316 | if (uids != NULL && uids[ii] != NULL) 1317 | mod_contact = e_contact_new_from_vcard_with_uid (vcards[ii], uids[ii]); 1318 | else 1319 | mod_contact = e_contact_new_from_vcard (vcards[ii]); 1320 | id = e_contact_get (mod_contact, E_CONTACT_UID); 1321 | 1322 | if (id == NULL) { 1323 | status = STATUS_ERROR; 1324 | 1325 | g_set_error_literal ( 1326 | error, E_CLIENT_ERROR, 1327 | E_CLIENT_ERROR_OTHER_ERROR, 1328 | _("No UID in the contact")); 1329 | g_object_unref (mod_contact); 1330 | break; 1331 | } 1332 | 1333 | if (update_decsync) { 1334 | path[0] = "resources"; 1335 | path[1] = id; 1336 | key_node = json_node_new (JSON_NODE_NULL); 1337 | key_string = json_to_string (key_node, FALSE); 1338 | value_node = json_node_new (JSON_NODE_VALUE); 1339 | json_node_set_string (value_node, vcards[ii]); 1340 | value_string = json_to_string (value_node, FALSE); 1341 | decsync_set_entry (bf->priv->decsync, path, 2, key_string, value_string); 1342 | json_node_free (key_node); 1343 | g_free (key_string); 1344 | json_node_free (value_node); 1345 | g_free (value_string); 1346 | } 1347 | 1348 | if (!e_book_sqlite_get_contact (bf->priv->sqlitedb, 1349 | id, FALSE, &old_contact, 1350 | &local_error)) { 1351 | g_warning (G_STRLOC ": Failed to load contact %s: %s", id, local_error->message); 1352 | g_propagate_error (error, local_error); 1353 | local_error = NULL; 1354 | 1355 | status = STATUS_ERROR; 1356 | 1357 | g_free (id); 1358 | g_object_unref (mod_contact); 1359 | break; 1360 | } 1361 | 1362 | if (bf->priv->revision_guards) { 1363 | mod_contact_rev = e_contact_get_const (mod_contact, E_CONTACT_REV); 1364 | old_contact_rev = e_contact_get_const (old_contact, E_CONTACT_REV); 1365 | 1366 | if (!mod_contact_rev || !old_contact_rev || 1367 | strcmp (mod_contact_rev, old_contact_rev) != 0) { 1368 | g_set_error ( 1369 | error, E_CLIENT_ERROR, 1370 | E_CLIENT_ERROR_OUT_OF_SYNC, 1371 | _("Tried to modify contact “%s” with out of sync revision"), 1372 | (gchar *) e_contact_get_const (mod_contact, E_CONTACT_UID)); 1373 | 1374 | status = STATUS_ERROR; 1375 | 1376 | g_free (id); 1377 | g_object_unref (mod_contact); 1378 | g_object_unref (old_contact); 1379 | break; 1380 | } 1381 | } 1382 | 1383 | /* Transform incomming photo blobs to uris before storing this to the DB */ 1384 | status = maybe_transform_vcard_for_photo (bf, old_contact, mod_contact, &local_error); 1385 | if (status == STATUS_ERROR) { 1386 | g_warning (G_STRLOC ": Error transforming contact %s: %s", id, local_error->message); 1387 | g_propagate_error (error, local_error); 1388 | local_error = NULL; 1389 | 1390 | g_free (id); 1391 | g_object_unref (old_contact); 1392 | g_object_unref (mod_contact); 1393 | break; 1394 | } 1395 | 1396 | /* update the revision (modified time of contact) */ 1397 | set_revision (bf, mod_contact); 1398 | 1399 | old_contacts = g_slist_prepend (old_contacts, old_contact); 1400 | *out_contacts = g_slist_prepend (*out_contacts, mod_contact); 1401 | 1402 | ids = g_slist_prepend (ids, id); 1403 | } 1404 | 1405 | if (status != STATUS_ERROR) { 1406 | GSList *old_link, *mod_link; 1407 | 1408 | /* Delete old photo file uris if need be (this will compare the new contact 1409 | * with the current copy in the BDB to extract the uris to delete) */ 1410 | old_link = old_contacts; 1411 | mod_link = *out_contacts; 1412 | 1413 | while (old_link != NULL && mod_link != NULL) { 1414 | maybe_delete_unused_uris ( 1415 | bf, 1416 | E_CONTACT (old_link->data), 1417 | E_CONTACT (mod_link->data)); 1418 | old_link = g_slist_next (old_link); 1419 | mod_link = g_slist_next (mod_link); 1420 | } 1421 | 1422 | /* Update summary as well */ 1423 | e_book_sqlite_add_contacts ( 1424 | bf->priv->sqlitedb, 1425 | *out_contacts, NULL, TRUE, 1426 | cancellable, &local_error); 1427 | 1428 | if (local_error != NULL) { 1429 | g_warning ( 1430 | "Failed to modify contacts: %s", 1431 | local_error->message); 1432 | g_propagate_error (error, local_error); 1433 | local_error = NULL; 1434 | 1435 | status = STATUS_ERROR; 1436 | } 1437 | } 1438 | 1439 | /* Bump the revision atomically in the same transaction */ 1440 | if (status != STATUS_ERROR) { 1441 | if (!e_book_backend_decsync_bump_revision (bf, error)) 1442 | status = STATUS_ERROR; 1443 | } 1444 | 1445 | /* Commit or rollback transaction */ 1446 | if (status != STATUS_ERROR) { 1447 | gboolean success; 1448 | 1449 | success = e_book_sqlite_unlock ( 1450 | bf->priv->sqlitedb, 1451 | EBSQL_UNLOCK_COMMIT, 1452 | error); 1453 | if (!success) 1454 | status = STATUS_ERROR; 1455 | 1456 | } else { 1457 | /* Rollback transaction */ 1458 | e_book_sqlite_unlock ( 1459 | bf->priv->sqlitedb, 1460 | EBSQL_UNLOCK_ROLLBACK, 1461 | &local_error); 1462 | 1463 | if (local_error != NULL) { 1464 | g_warning ( 1465 | "Failed to rollback transaction after " 1466 | "failing to modify contacts: %s", 1467 | local_error->message); 1468 | g_clear_error (&local_error); 1469 | } 1470 | } 1471 | 1472 | if (status != STATUS_ERROR) { 1473 | *out_contacts = g_slist_reverse (*out_contacts); 1474 | } else { 1475 | g_slist_free_full (*out_contacts, g_object_unref); 1476 | *out_contacts = NULL; 1477 | } 1478 | 1479 | /* Now that we've modified the contact(s), 1480 | * notify cursors of the changes. */ 1481 | if (status != STATUS_ERROR) { 1482 | GSList *link; 1483 | 1484 | for (link = old_contacts; link; link = g_slist_next (link)) { 1485 | cursors_contact_removed (bf, E_CONTACT (link->data)); 1486 | } 1487 | 1488 | for (link = *out_contacts; link; link = g_slist_next (link)) { 1489 | cursors_contact_added (bf, E_CONTACT (link->data)); 1490 | } 1491 | } 1492 | 1493 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1494 | 1495 | g_slist_free_full (old_contacts, g_object_unref); 1496 | g_slist_free_full (ids, g_free); 1497 | 1498 | return (status != STATUS_ERROR); 1499 | } 1500 | 1501 | static gboolean 1502 | book_backend_decsync_modify_contacts_sync (EBookBackendSync *backend, 1503 | const gchar * const *vcards, 1504 | guint32 opflags, 1505 | GSList **out_contacts, 1506 | GCancellable *cancellable, 1507 | GError **error) 1508 | { 1509 | return book_backend_decsync_modify_contacts_sync_with_decsync (backend, vcards, NULL, opflags, out_contacts, cancellable, error, TRUE); 1510 | } 1511 | 1512 | static gboolean 1513 | book_backend_decsync_remove_contacts_sync_with_decsync (EBookBackendSync *backend, 1514 | const gchar * const *uids, 1515 | guint32 opflags, 1516 | GSList **out_removed_uids, 1517 | GCancellable *cancellable, 1518 | GError **error, 1519 | gboolean update_decsync) 1520 | { 1521 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1522 | GSList *removed_ids = NULL, *removed_contacts = NULL; 1523 | GError *local_error = NULL; 1524 | const GSList *l; 1525 | gboolean success = TRUE; 1526 | guint ii, length; 1527 | const gchar *path[2]; 1528 | JsonNode *key_node, *value_node; 1529 | gchar *key_string, *value_string; 1530 | 1531 | g_return_val_if_fail (out_removed_uids != NULL, FALSE); 1532 | 1533 | length = g_strv_length ((gchar **) uids); 1534 | 1535 | g_rw_lock_writer_lock (&(bf->priv->lock)); 1536 | 1537 | if (!e_book_sqlite_lock (bf->priv->sqlitedb, 1538 | EBSQL_LOCK_WRITE, 1539 | cancellable, error)) { 1540 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1541 | return FALSE; 1542 | } 1543 | 1544 | for (ii = 0; ii < length && success; ii++) { 1545 | EContact *contact = NULL; 1546 | 1547 | if (update_decsync) { 1548 | path[0] = "resources"; 1549 | path[1] = uids[ii]; 1550 | key_node = json_node_new (JSON_NODE_NULL); 1551 | key_string = json_to_string (key_node, FALSE); 1552 | value_node = json_node_new (JSON_NODE_NULL); 1553 | value_string = json_to_string (value_node, FALSE); 1554 | decsync_set_entry (bf->priv->decsync, path, 2, key_string, value_string); 1555 | json_node_free (key_node); 1556 | g_free (key_string); 1557 | json_node_free (value_node); 1558 | g_free (value_string); 1559 | } 1560 | 1561 | /* First load the EContacts which need to be removed, we might delete some 1562 | * photos from disk because of this... 1563 | * 1564 | * Note: sqlite backend can probably make this faster by executing a 1565 | * single query to fetch a list of contacts for a list of ids, the 1566 | * current method makes a query for each UID. 1567 | */ 1568 | if (e_book_sqlite_get_contact (bf->priv->sqlitedb, 1569 | uids[ii], FALSE, &contact, 1570 | &local_error)) { 1571 | removed_ids = g_slist_prepend (removed_ids, g_strdup (uids[ii])); 1572 | removed_contacts = g_slist_prepend (removed_contacts, contact); 1573 | } else { 1574 | 1575 | if (g_error_matches (local_error, 1576 | E_BOOK_SQLITE_ERROR, 1577 | E_BOOK_SQLITE_ERROR_CONTACT_NOT_FOUND)) { 1578 | g_set_error ( 1579 | error, E_BOOK_CLIENT_ERROR, 1580 | E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, 1581 | _("Contact “%s” not found"), uids[ii]); 1582 | g_error_free (local_error); 1583 | } else { 1584 | g_warning ("Failed to fetch contact to be removed: %s", local_error->message); 1585 | g_propagate_error (error, local_error); 1586 | local_error = NULL; 1587 | } 1588 | /* Abort as soon as missing contact is to be deleted */ 1589 | success = FALSE; 1590 | break; 1591 | } 1592 | } 1593 | 1594 | if (success) { 1595 | 1596 | /* Delete URI associated to those contacts */ 1597 | for (l = removed_contacts; l; l = l->next) { 1598 | maybe_delete_unused_uris (bf, E_CONTACT (l->data), NULL); 1599 | } 1600 | 1601 | /* Remove from summary as well */ 1602 | if (!e_book_sqlite_remove_contacts (bf->priv->sqlitedb, removed_ids, 1603 | cancellable, &local_error)) { 1604 | if (local_error) { 1605 | g_warning ("Failed to remove contacts: %s", local_error->message); 1606 | g_propagate_error (error, local_error); 1607 | } 1608 | } 1609 | 1610 | e_book_backend_decsync_bump_revision (bf, NULL); 1611 | } 1612 | 1613 | /* Commit or rollback transaction */ 1614 | if (success) { 1615 | success = e_book_sqlite_unlock (bf->priv->sqlitedb, EBSQL_UNLOCK_COMMIT, error); 1616 | } else { 1617 | /* Rollback transaction */ 1618 | if (!e_book_sqlite_unlock (bf->priv->sqlitedb, EBSQL_UNLOCK_ROLLBACK, &local_error)) { 1619 | g_warning ( 1620 | "Failed to rollback transaction after failing to modify contacts: %s", 1621 | local_error->message); 1622 | g_clear_error (&local_error); 1623 | } 1624 | } 1625 | 1626 | /* After removing any contacts, notify any cursors that the new contacts are added */ 1627 | if (success) { 1628 | for (l = removed_contacts; l; l = l->next) { 1629 | cursors_contact_removed (bf, E_CONTACT (l->data)); 1630 | } 1631 | } 1632 | 1633 | *out_removed_uids = removed_ids; 1634 | 1635 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1636 | 1637 | g_slist_free_full (removed_contacts, (GDestroyNotify) g_object_unref); 1638 | 1639 | return success; 1640 | } 1641 | 1642 | static gboolean 1643 | book_backend_decsync_remove_contacts_sync (EBookBackendSync *backend, 1644 | const gchar * const *uids, 1645 | guint32 opflags, 1646 | GSList **out_removed_uids, 1647 | GCancellable *cancellable, 1648 | GError **error) 1649 | { 1650 | return book_backend_decsync_remove_contacts_sync_with_decsync (backend, uids, opflags, out_removed_uids, cancellable, error, TRUE); 1651 | } 1652 | 1653 | static EContact * 1654 | book_backend_decsync_get_contact_sync (EBookBackendSync *backend, 1655 | const gchar *uid, 1656 | GCancellable *cancellable, 1657 | GError **error) 1658 | { 1659 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1660 | EContact *contact = NULL; 1661 | gboolean success; 1662 | GError *local_error = NULL; 1663 | 1664 | g_rw_lock_reader_lock (&(bf->priv->lock)); 1665 | success = e_book_sqlite_get_contact ( 1666 | bf->priv->sqlitedb, 1667 | uid, FALSE, &contact, 1668 | &local_error); 1669 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 1670 | 1671 | if (!success) { 1672 | if (g_error_matches (local_error, 1673 | E_BOOK_SQLITE_ERROR, 1674 | E_BOOK_SQLITE_ERROR_CONTACT_NOT_FOUND)) { 1675 | g_set_error ( 1676 | error, E_BOOK_CLIENT_ERROR, 1677 | E_BOOK_CLIENT_ERROR_CONTACT_NOT_FOUND, 1678 | _("Contact “%s” not found"), uid); 1679 | g_error_free (local_error); 1680 | } else 1681 | g_propagate_error (error, local_error); 1682 | } 1683 | 1684 | return contact; 1685 | } 1686 | 1687 | static gboolean 1688 | book_backend_decsync_get_contact_list_sync (EBookBackendSync *backend, 1689 | const gchar *query, 1690 | GSList **out_contacts, 1691 | GCancellable *cancellable, 1692 | GError **error) 1693 | { 1694 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1695 | GSList *summary_list = NULL; 1696 | GSList *link; 1697 | gboolean success = TRUE; 1698 | GError *local_error = NULL; 1699 | 1700 | g_return_val_if_fail (out_contacts != NULL, FALSE); 1701 | 1702 | *out_contacts = NULL; 1703 | 1704 | d (printf ("book_backend_decsync_get_contact_list_sync (%s)\n", query)); 1705 | 1706 | g_rw_lock_reader_lock (&(bf->priv->lock)); 1707 | 1708 | success = e_book_sqlite_lock ( 1709 | bf->priv->sqlitedb, 1710 | EBSQL_LOCK_READ, 1711 | cancellable, error); 1712 | if (!success) { 1713 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1714 | return FALSE; 1715 | } 1716 | 1717 | success = e_book_sqlite_search ( 1718 | bf->priv->sqlitedb, 1719 | query, 1720 | FALSE, 1721 | &summary_list, 1722 | cancellable, 1723 | &local_error); 1724 | 1725 | e_book_sqlite_unlock ( 1726 | bf->priv->sqlitedb, 1727 | EBSQL_UNLOCK_NONE, 1728 | success ? &local_error : NULL); 1729 | 1730 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 1731 | 1732 | if (!success) { 1733 | 1734 | g_warn_if_fail (summary_list == NULL); 1735 | 1736 | if (g_error_matches (local_error, 1737 | E_BOOK_SQLITE_ERROR, 1738 | E_BOOK_SQLITE_ERROR_UNSUPPORTED_QUERY)) { 1739 | g_set_error ( 1740 | error, E_CLIENT_ERROR, 1741 | E_CLIENT_ERROR_NOT_SUPPORTED, 1742 | _("Query “%s” not supported"), query); 1743 | g_error_free (local_error); 1744 | 1745 | } else if (g_error_matches (local_error, 1746 | E_BOOK_SQLITE_ERROR, 1747 | E_BOOK_SQLITE_ERROR_INVALID_QUERY)) { 1748 | g_set_error ( 1749 | error, E_CLIENT_ERROR, 1750 | E_CLIENT_ERROR_INVALID_QUERY, 1751 | _("Invalid Query “%s”"), query); 1752 | g_error_free (local_error); 1753 | 1754 | } else { 1755 | g_warning ("Failed to fetch contact ids: %s", local_error->message); 1756 | g_propagate_error (error, local_error); 1757 | } 1758 | } 1759 | 1760 | for (link = summary_list; link != NULL; link = g_slist_next (link)) { 1761 | EbSqlSearchData *data = link->data; 1762 | EContact *contact; 1763 | 1764 | contact = e_contact_new_from_vcard (data->vcard); 1765 | link->data = contact; 1766 | 1767 | e_book_sqlite_search_data_free (data); 1768 | } 1769 | 1770 | *out_contacts = summary_list; 1771 | 1772 | return success; 1773 | } 1774 | 1775 | static gboolean 1776 | book_backend_decsync_get_contact_list_uids_sync (EBookBackendSync *backend, 1777 | const gchar *query, 1778 | GSList **out_uids, 1779 | GCancellable *cancellable, 1780 | GError **error) 1781 | { 1782 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 1783 | gboolean success = TRUE; 1784 | GError *local_error = NULL; 1785 | 1786 | g_return_val_if_fail (out_uids != NULL, FALSE); 1787 | 1788 | *out_uids = NULL; 1789 | 1790 | d (printf ("book_backend_decsync_get_contact_list_sync (%s)\n", query)); 1791 | 1792 | g_rw_lock_reader_lock (&(bf->priv->lock)); 1793 | 1794 | success = e_book_sqlite_lock ( 1795 | bf->priv->sqlitedb, 1796 | EBSQL_LOCK_READ, 1797 | cancellable, error); 1798 | if (!success) { 1799 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 1800 | return FALSE; 1801 | } 1802 | 1803 | success = e_book_sqlite_search_uids ( 1804 | bf->priv->sqlitedb, 1805 | query, 1806 | out_uids, 1807 | cancellable, 1808 | &local_error); 1809 | e_book_sqlite_unlock ( 1810 | bf->priv->sqlitedb, 1811 | EBSQL_UNLOCK_NONE, 1812 | success ? &local_error : NULL); 1813 | 1814 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 1815 | 1816 | if (!success) { 1817 | g_warn_if_fail (*out_uids == NULL); 1818 | 1819 | if (g_error_matches (local_error, 1820 | E_BOOK_SQLITE_ERROR, 1821 | E_BOOK_SQLITE_ERROR_UNSUPPORTED_QUERY)) { 1822 | g_set_error ( 1823 | error, E_CLIENT_ERROR, 1824 | E_CLIENT_ERROR_NOT_SUPPORTED, 1825 | _("Query “%s” not supported"), query); 1826 | g_error_free (local_error); 1827 | 1828 | } else if (g_error_matches (local_error, 1829 | E_BOOK_SQLITE_ERROR, 1830 | E_BOOK_SQLITE_ERROR_INVALID_QUERY)) { 1831 | g_set_error ( 1832 | error, E_CLIENT_ERROR, 1833 | E_CLIENT_ERROR_INVALID_QUERY, 1834 | _("Invalid Query “%s”"), query); 1835 | g_error_free (local_error); 1836 | 1837 | } else { 1838 | g_warning ( 1839 | "Failed to fetch contact ids: %s", 1840 | local_error->message); 1841 | g_propagate_error (error, local_error); 1842 | } 1843 | } 1844 | 1845 | return success; 1846 | } 1847 | 1848 | static gboolean 1849 | book_backend_decsync_gather_addresses_cb (gpointer ptr_name, 1850 | gpointer ptr_email, 1851 | gpointer user_data) 1852 | { 1853 | GPtrArray *array = user_data; 1854 | const gchar *email = ptr_email; 1855 | 1856 | if (email && *email) 1857 | g_ptr_array_add (array, e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_IS, email)); 1858 | 1859 | return TRUE; 1860 | } 1861 | 1862 | static gboolean 1863 | book_backend_decsync_contains_email_sync (EBookBackendSync *backend, 1864 | const gchar *email_address, 1865 | GCancellable *cancellable, 1866 | GError **error) 1867 | { 1868 | EBookQuery *book_query = NULL; 1869 | GPtrArray *array; 1870 | gchar *sexp = NULL; 1871 | gboolean success = FALSE; 1872 | 1873 | g_return_val_if_fail (email_address != NULL, FALSE); 1874 | 1875 | d (printf ("book_backend_decsync_contains_email_sync (%s)\n", email_address)); 1876 | 1877 | array = g_ptr_array_new_full (1, (GDestroyNotify) e_book_query_unref); 1878 | 1879 | e_book_util_foreach_address (email_address, book_backend_decsync_gather_addresses_cb, array); 1880 | 1881 | if (array->len > 0) 1882 | book_query = e_book_query_or (array->len, (EBookQuery **) array->pdata, FALSE); 1883 | 1884 | if (book_query) 1885 | sexp = e_book_query_to_string (book_query); 1886 | 1887 | if (sexp) { 1888 | GSList *uids = NULL; 1889 | 1890 | success = book_backend_decsync_get_contact_list_uids_sync (backend, sexp, 1891 | &uids, cancellable, error); 1892 | success = success && uids != NULL; 1893 | 1894 | g_slist_free_full (uids, g_free); 1895 | } 1896 | 1897 | g_clear_pointer (&book_query, e_book_query_unref); 1898 | g_ptr_array_unref (array); 1899 | g_free (sexp); 1900 | 1901 | return success; 1902 | } 1903 | 1904 | static void 1905 | book_backend_decsync_start_view (EBookBackend *backend, 1906 | EDataBookView *book_view) 1907 | { 1908 | DecsyncBackendSearchClosure *closure; 1909 | 1910 | closure = init_closure (book_view, E_BOOK_BACKEND_DECSYNC (backend)); 1911 | 1912 | d (printf ("starting book view thread\n")); 1913 | closure->thread = g_thread_new (NULL, book_view_thread, book_view); 1914 | 1915 | e_flag_wait (closure->running); 1916 | 1917 | /* at this point we know the book view thread is actually running */ 1918 | d (printf ("returning from start_view\n")); 1919 | } 1920 | 1921 | static void 1922 | book_backend_decsync_stop_view (EBookBackend *backend, 1923 | EDataBookView *book_view) 1924 | { 1925 | DecsyncBackendSearchClosure *closure = get_closure (book_view); 1926 | gboolean need_join; 1927 | 1928 | if (!closure) 1929 | return; 1930 | 1931 | d (printf ("stopping query\n")); 1932 | need_join = e_flag_is_set (closure->running); 1933 | e_flag_clear (closure->running); 1934 | 1935 | if (need_join) { 1936 | g_thread_join (closure->thread); 1937 | closure->thread = NULL; 1938 | } 1939 | } 1940 | 1941 | static EDataBookDirect * 1942 | book_backend_decsync_get_direct_book (EBookBackend *backend) 1943 | { 1944 | EDataBookDirect *direct; 1945 | ESourceRegistry *registry; 1946 | ESource *source; 1947 | gchar *backend_path; 1948 | gchar *dirname; 1949 | const gchar *modules_env = NULL; 1950 | 1951 | modules_env = g_getenv (EDS_ADDRESS_BOOK_MODULES); 1952 | 1953 | source = e_backend_get_source (E_BACKEND (backend)); 1954 | registry = e_book_backend_get_registry (backend); 1955 | dirname = e_book_backend_decsync_extract_path_from_source ( 1956 | registry, source, GET_PATH_DB_DIR); 1957 | 1958 | /* Support in-tree testing / relocated modules */ 1959 | if (modules_env) { 1960 | backend_path = g_build_filename ( 1961 | modules_env, "libebookbackenddecsync.so", NULL); 1962 | } else { 1963 | backend_path = g_build_filename ( 1964 | BACKENDDIR, "libebookbackenddecsync.so", NULL); 1965 | } 1966 | 1967 | direct = e_data_book_direct_new ( 1968 | backend_path, "EBookBackendDecsyncFactory", dirname); 1969 | 1970 | g_free (backend_path); 1971 | g_free (dirname); 1972 | 1973 | return direct; 1974 | } 1975 | 1976 | static void 1977 | book_backend_decsync_configure_direct (EBookBackend *backend, 1978 | const gchar *config) 1979 | { 1980 | EBookBackendDecsyncPrivate *priv; 1981 | 1982 | priv = E_BOOK_BACKEND_DECSYNC (backend)->priv; 1983 | priv->base_directory = g_strdup (config); 1984 | } 1985 | 1986 | static void 1987 | book_backend_decsync_vcard_changed (EbSqlChangeType change_type, 1988 | const gchar *uid, 1989 | const gchar *extra, 1990 | const gchar *vcard, 1991 | gpointer user_data) 1992 | { 1993 | EBookBackend *backend = E_BOOK_BACKEND (user_data); 1994 | EContact *contact; 1995 | 1996 | if (change_type == EBSQL_CHANGE_LOCALE_CHANGED) { 1997 | contact = e_contact_new_from_vcard_with_uid (vcard, uid); 1998 | e_book_backend_notify_update (backend, contact); 1999 | g_object_unref (contact); 2000 | } 2001 | } 2002 | 2003 | static gboolean 2004 | book_backend_decsync_set_locale (EBookBackend *backend, 2005 | const gchar *locale, 2006 | GCancellable *cancellable, 2007 | GError **error) 2008 | { 2009 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 2010 | gboolean success; 2011 | GList *l; 2012 | 2013 | g_rw_lock_writer_lock (&(bf->priv->lock)); 2014 | 2015 | success = e_book_sqlite_lock ( 2016 | bf->priv->sqlitedb, 2017 | EBSQL_LOCK_WRITE, 2018 | cancellable, error); 2019 | if (!success) { 2020 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 2021 | return FALSE; 2022 | } 2023 | 2024 | success = e_book_sqlite_set_locale ( 2025 | bf->priv->sqlitedb, locale, 2026 | cancellable, error); 2027 | 2028 | if (success) 2029 | success = e_book_backend_decsync_bump_revision (bf, error); 2030 | 2031 | if (success) { 2032 | success = e_book_sqlite_unlock ( 2033 | bf->priv->sqlitedb, 2034 | EBSQL_UNLOCK_COMMIT, 2035 | error); 2036 | 2037 | } else { 2038 | GError *local_error = NULL; 2039 | 2040 | /* Rollback transaction */ 2041 | e_book_sqlite_unlock ( 2042 | bf->priv->sqlitedb, 2043 | EBSQL_UNLOCK_ROLLBACK, 2044 | &local_error); 2045 | 2046 | if (local_error != NULL) { 2047 | g_warning ( 2048 | "Failed to rollback transaction " 2049 | "after failing to set locale: %s", 2050 | local_error->message); 2051 | g_clear_error (&local_error); 2052 | } 2053 | } 2054 | 2055 | /* This must be done outside the EBookSqlite lock, 2056 | * as it may try to acquire the lock as well. */ 2057 | for (l = bf->priv->cursors; success && l; l = l->next) { 2058 | EDataBookCursor *cursor = l->data; 2059 | 2060 | success = e_data_book_cursor_load_locale ( 2061 | cursor, NULL, cancellable, error); 2062 | } 2063 | 2064 | /* We set the new locale, now update our local variable */ 2065 | if (success) { 2066 | g_free (bf->priv->locale); 2067 | bf->priv->locale = g_strdup (locale); 2068 | } 2069 | 2070 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 2071 | 2072 | return success; 2073 | } 2074 | 2075 | static gchar * 2076 | book_backend_decsync_dup_locale (EBookBackend *backend) 2077 | { 2078 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 2079 | gchar *locale; 2080 | 2081 | g_rw_lock_reader_lock (&(bf->priv->lock)); 2082 | locale = g_strdup (bf->priv->locale); 2083 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 2084 | 2085 | return locale; 2086 | } 2087 | 2088 | static EDataBookCursor * 2089 | book_backend_decsync_create_cursor (EBookBackend *backend, 2090 | EContactField *sort_fields, 2091 | EBookCursorSortType *sort_types, 2092 | guint n_fields, 2093 | GError **error) 2094 | { 2095 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 2096 | EDataBookCursor *cursor; 2097 | 2098 | g_rw_lock_writer_lock (&(bf->priv->lock)); 2099 | 2100 | cursor = e_data_book_cursor_sqlite_new ( 2101 | backend, 2102 | bf->priv->sqlitedb, 2103 | SQLITE_REVISION_KEY, 2104 | sort_fields, 2105 | sort_types, 2106 | n_fields, 2107 | error); 2108 | 2109 | if (cursor != NULL) { 2110 | bf->priv->cursors = 2111 | g_list_prepend (bf->priv->cursors, cursor); 2112 | } 2113 | 2114 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 2115 | 2116 | return cursor; 2117 | } 2118 | 2119 | static gboolean 2120 | book_backend_decsync_delete_cursor (EBookBackend *backend, 2121 | EDataBookCursor *cursor, 2122 | GError **error) 2123 | { 2124 | EBookBackendDecsync *bf = E_BOOK_BACKEND_DECSYNC (backend); 2125 | GList *link; 2126 | 2127 | g_rw_lock_writer_lock (&(bf->priv->lock)); 2128 | 2129 | link = g_list_find (bf->priv->cursors, cursor); 2130 | 2131 | if (link != NULL) { 2132 | bf->priv->cursors = g_list_delete_link (bf->priv->cursors, link); 2133 | g_object_unref (cursor); 2134 | } else { 2135 | g_set_error_literal ( 2136 | error, 2137 | E_CLIENT_ERROR, 2138 | E_CLIENT_ERROR_INVALID_ARG, 2139 | _("Requested to delete an unrelated cursor")); 2140 | } 2141 | 2142 | g_rw_lock_writer_unlock (&(bf->priv->lock)); 2143 | 2144 | return link != NULL; 2145 | } 2146 | 2147 | 2148 | /**************************************************************** 2149 | * DecSync updates * 2150 | ****************************************************************/ 2151 | 2152 | typedef struct { 2153 | EBookBackend *backend; 2154 | } Extra; 2155 | 2156 | static void 2157 | deleteBook (Extra *extra) 2158 | { 2159 | ESource *source; 2160 | 2161 | source = e_backend_get_source (E_BACKEND (extra->backend)); 2162 | e_source_remove_sync (source, NULL, NULL); 2163 | } 2164 | 2165 | static void 2166 | updateName (Extra *extra, const gchar *name) 2167 | { 2168 | EBookBackend *backend; 2169 | ESource *source; 2170 | const gchar *old_name; 2171 | 2172 | backend = E_BOOK_BACKEND (extra->backend); 2173 | source = e_backend_get_source (E_BACKEND (backend)); 2174 | 2175 | old_name = e_source_get_display_name (source); 2176 | if (g_strcmp0 (old_name, name)) { 2177 | e_source_set_display_name (source, name); 2178 | e_source_write_sync (source, NULL, NULL); 2179 | } 2180 | } 2181 | 2182 | static void 2183 | updateContacts (const gchar *uid, const gchar *vcard, Extra *extra) 2184 | { 2185 | EBookBackend *backend; 2186 | EBookBackendSync *backend_sync; 2187 | EBookBackendDecsync *bf; 2188 | gboolean success, exists; 2189 | const gchar *uids[2], *vcards[2]; 2190 | GSList *out_contacts = NULL, *link; 2191 | 2192 | backend = E_BOOK_BACKEND (extra->backend); 2193 | backend_sync = E_BOOK_BACKEND_SYNC (backend); 2194 | bf = E_BOOK_BACKEND_DECSYNC (backend); 2195 | 2196 | g_rw_lock_reader_lock (&(bf->priv->lock)); 2197 | success = e_book_sqlite_has_contact (bf->priv->sqlitedb, uid, &exists, NULL); 2198 | g_rw_lock_reader_unlock (&(bf->priv->lock)); 2199 | if (!success) return; 2200 | 2201 | uids[0] = uid; 2202 | uids[1] = '\0'; 2203 | vcards[0] = vcard; 2204 | vcards[1] = '\0'; 2205 | if (exists) 2206 | book_backend_decsync_modify_contacts_sync_with_decsync (backend_sync, vcards, uids, 0, &out_contacts, NULL, NULL, FALSE); 2207 | else 2208 | book_backend_decsync_create_contacts_sync_with_decsync (backend_sync, vcards, uids, 0, &out_contacts, NULL, NULL, FALSE); 2209 | for (link = out_contacts; link; link = g_slist_next (link)) { 2210 | e_book_backend_notify_update (backend, E_CONTACT (link->data)); 2211 | } 2212 | 2213 | g_slist_free_full (out_contacts, g_object_unref); 2214 | } 2215 | 2216 | static void 2217 | removeContacts (const gchar *uid, Extra *extra) 2218 | { 2219 | EBookBackend *backend; 2220 | EBookBackendSync *backend_sync; 2221 | const gchar *uids[2]; 2222 | GSList *out_uids; 2223 | 2224 | backend = E_BOOK_BACKEND (extra->backend); 2225 | backend_sync = E_BOOK_BACKEND_SYNC (backend); 2226 | uids[0] = uid; 2227 | uids[1] = '\0'; 2228 | book_backend_decsync_remove_contacts_sync_with_decsync (backend_sync, uids, 0, &out_uids, NULL, NULL, FALSE); 2229 | e_book_backend_notify_remove (backend, uid); 2230 | 2231 | g_slist_free_full (out_uids, g_free); 2232 | } 2233 | 2234 | static void 2235 | infoListener (const gchar **path, int len, const char *datetime, const char *key_string, const char *value_string, void *extra_void) 2236 | { 2237 | Extra *extra; 2238 | const gchar *info; 2239 | JsonNode *key_node, *value_node; 2240 | GError *error = NULL; 2241 | 2242 | extra = (Extra*)extra_void; 2243 | key_node = json_from_string (key_string, &error); 2244 | if (error != NULL) { 2245 | g_warning ("Invalid JSON for info key: %s", key_string); 2246 | g_error_free (error); 2247 | return; 2248 | } 2249 | value_node = json_from_string (value_string, &error); 2250 | if (error != NULL) { 2251 | g_warning ("Invalid JSON for info value: %s", value_string); 2252 | json_node_free (key_node); 2253 | g_error_free (error); 2254 | return; 2255 | } 2256 | info = json_node_get_string (key_node); 2257 | if (g_strcmp0 (info, "deleted") == 0) { 2258 | if (json_node_get_boolean (value_node) == TRUE) { 2259 | deleteBook (extra); 2260 | } 2261 | } else if (g_strcmp0 (info, "name") == 0) { 2262 | updateName(extra, json_node_get_string (value_node)); 2263 | } else { 2264 | g_warning ("Unknown info key: %s", info); 2265 | } 2266 | json_node_free (key_node); 2267 | json_node_free (value_node); 2268 | } 2269 | 2270 | static void 2271 | resourcesListener (const gchar **path, int len, const char *datetime, const char *key_string, const char *value_string, void *extra_void) 2272 | { 2273 | Extra *extra; 2274 | const gchar *uid, *vcard; 2275 | JsonNode *key_node, *value_node; 2276 | GError *error = NULL; 2277 | 2278 | extra = (Extra*)extra_void; 2279 | key_node = json_from_string (key_string, &error); 2280 | if (error != NULL) { 2281 | g_warning ("Invalid JSON for resource key: %s", key_string); 2282 | g_error_free (error); 2283 | return; 2284 | } 2285 | value_node = json_from_string (value_string, &error); 2286 | if (error != NULL) { 2287 | g_warning ("Invalid JSON for resource value: %s", value_string); 2288 | json_node_free (key_node); 2289 | g_error_free (error); 2290 | return; 2291 | } 2292 | if (len != 1) { 2293 | g_warning ("Invalid resources path size %i", len); 2294 | return; 2295 | } 2296 | uid = path[0]; 2297 | if (json_node_is_null (value_node)) { 2298 | removeContacts(uid, extra); 2299 | } else { 2300 | vcard = json_node_get_string (value_node); 2301 | updateContacts(uid, vcard, extra); 2302 | } 2303 | json_node_free (key_node); 2304 | json_node_free (value_node); 2305 | } 2306 | 2307 | static gboolean 2308 | getDecsyncFromSource (EBookBackendDecsyncPrivate *priv, ESource *source) 2309 | { 2310 | ESourceDecsync *decsync_extension; 2311 | const gchar *extension_name, *decsync_dir, *collection, *appid, *path[1]; 2312 | int error; 2313 | 2314 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 2315 | decsync_extension = e_source_get_extension (source, extension_name); 2316 | decsync_dir = e_source_decsync_get_decsync_dir (decsync_extension); 2317 | collection = e_source_decsync_get_collection (decsync_extension); 2318 | appid = e_source_decsync_get_appid (decsync_extension); 2319 | error = decsync_new (&priv->decsync, decsync_dir, "contacts", collection, appid); 2320 | if (error != 0) { 2321 | return FALSE; 2322 | } 2323 | path[0] = "info"; 2324 | decsync_add_listener (priv->decsync, path, 1, infoListener); 2325 | path[0] = "resources"; 2326 | decsync_add_listener (priv->decsync, path, 1, resourcesListener); 2327 | decsync_init_done (priv->decsync); 2328 | return TRUE; 2329 | } 2330 | 2331 | static gboolean 2332 | book_backend_decsync_refresh_cb (gpointer backend) 2333 | { 2334 | EBookBackendDecsync *bf; 2335 | Extra extra; 2336 | 2337 | bf = E_BOOK_BACKEND_DECSYNC (backend); 2338 | extra = (Extra) {backend}; 2339 | decsync_execute_all_new_entries (bf->priv->decsync, &extra); 2340 | return TRUE; 2341 | } 2342 | 2343 | static gboolean 2344 | book_backend_decsync_refresh_start (EBookBackendDecsync *bf) 2345 | { 2346 | ESource *source; 2347 | ESourceRefresh *extension; 2348 | const gchar *extension_name; 2349 | guint interval_in_minutes = 0; 2350 | 2351 | source = e_backend_get_source (E_BACKEND (bf)); 2352 | 2353 | extension_name = E_SOURCE_EXTENSION_REFRESH; 2354 | extension = e_source_get_extension (source, extension_name); 2355 | 2356 | if (e_source_refresh_get_enabled (extension)) { 2357 | interval_in_minutes = e_source_refresh_get_interval_minutes (extension); 2358 | if (interval_in_minutes == 0) 2359 | interval_in_minutes = 30; 2360 | } 2361 | 2362 | if (interval_in_minutes > 0) { 2363 | e_named_timeout_add_seconds (interval_in_minutes * 60, book_backend_decsync_refresh_cb, bf); 2364 | } 2365 | return FALSE; 2366 | } 2367 | 2368 | static void 2369 | book_backend_decsync_refresh (EBookBackend *backend, 2370 | EDataBook *book, 2371 | guint32 opid, 2372 | GCancellable *cancellable) 2373 | { 2374 | book_backend_decsync_refresh_cb (backend); 2375 | } 2376 | 2377 | static gboolean 2378 | book_backend_decsync_initable_init (GInitable *initable, 2379 | GCancellable *cancellable, 2380 | GError **error) 2381 | { 2382 | EBookBackendDecsyncPrivate *priv; 2383 | ESourceBackendSummarySetup *setup_extension; 2384 | ESourceRegistry *registry; 2385 | ESource *source; 2386 | const gchar *extension_name; 2387 | gchar *dirname, *fullpath; 2388 | gboolean success = TRUE; 2389 | 2390 | priv = E_BOOK_BACKEND_DECSYNC (initable)->priv; 2391 | 2392 | source = e_backend_get_source (E_BACKEND (initable)); 2393 | registry = e_book_backend_get_registry (E_BOOK_BACKEND (initable)); 2394 | 2395 | g_type_ensure (E_TYPE_SOURCE_BACKEND_SUMMARY_SETUP); 2396 | extension_name = E_SOURCE_EXTENSION_BACKEND_SUMMARY_SETUP; 2397 | setup_extension = e_source_get_extension (source, extension_name); 2398 | 2399 | if (priv->base_directory) 2400 | dirname = g_strdup (priv->base_directory); 2401 | else 2402 | dirname = e_book_backend_decsync_extract_path_from_source ( 2403 | registry, source, GET_PATH_DB_DIR); 2404 | 2405 | fullpath = g_build_filename (dirname, "contacts.db", NULL); 2406 | 2407 | success = getDecsyncFromSource (priv, source); 2408 | 2409 | if (!success) 2410 | goto exit; 2411 | 2412 | /* If we already have a handle on this, it means there 2413 | * was an old BDB migrated and no need to reopen it. */ 2414 | if (priv->sqlitedb == NULL) { 2415 | gint populated = 0; 2416 | GError *local_error = NULL; 2417 | 2418 | /* Ensure the directory exists first. */ 2419 | success = create_directory (dirname, error); 2420 | 2421 | if (!success) 2422 | goto exit; 2423 | 2424 | /* Create the sqlitedb. */ 2425 | priv->sqlitedb = e_book_sqlite_new_full ( 2426 | fullpath, source, setup_extension, 2427 | NULL, 2428 | book_backend_decsync_vcard_changed, 2429 | initable, NULL, cancellable, error); 2430 | 2431 | if (priv->sqlitedb == NULL) { 2432 | success = FALSE; 2433 | goto exit; 2434 | } 2435 | 2436 | /* An sqlite DB only 'exists' if the populated flag is set. */ 2437 | e_book_sqlite_get_key_value_int ( 2438 | priv->sqlitedb, 2439 | E_BOOK_SQL_IS_POPULATED_KEY, 2440 | &populated, 2441 | &local_error); 2442 | 2443 | if (local_error != NULL) { 2444 | g_propagate_error (error, local_error); 2445 | success = FALSE; 2446 | goto exit; 2447 | } 2448 | 2449 | if (!populated) { 2450 | /* Set the populated flag. */ 2451 | success = e_book_sqlite_set_key_value_int ( 2452 | priv->sqlitedb, 2453 | E_BOOK_SQL_IS_POPULATED_KEY, 2454 | TRUE, 2455 | error); 2456 | 2457 | if (!success) 2458 | goto exit; 2459 | } 2460 | } 2461 | 2462 | /* Load the locale */ 2463 | e_book_backend_decsync_load_locale (E_BOOK_BACKEND_DECSYNC (initable)); 2464 | 2465 | /* Resolve the photo directory here. */ 2466 | priv->photo_dirname = 2467 | e_book_backend_decsync_extract_path_from_source ( 2468 | registry, source, GET_PATH_PHOTO_DIR); 2469 | success = create_directory (priv->photo_dirname, error); 2470 | 2471 | exit: 2472 | g_free (dirname); 2473 | g_free (fullpath); 2474 | 2475 | return success; 2476 | } 2477 | 2478 | static void 2479 | e_book_backend_decsync_class_init (EBookBackendDecsyncClass *class) 2480 | { 2481 | GObjectClass *object_class; 2482 | EBookBackendClass *backend_class; 2483 | EBookBackendSyncClass *backend_sync_class; 2484 | 2485 | object_class = G_OBJECT_CLASS (class); 2486 | object_class->dispose = book_backend_decsync_dispose; 2487 | object_class->finalize = book_backend_decsync_finalize; 2488 | 2489 | backend_sync_class = E_BOOK_BACKEND_SYNC_CLASS (class); 2490 | backend_sync_class->open_sync = book_backend_decsync_open_sync; 2491 | backend_sync_class->create_contacts_sync = book_backend_decsync_create_contacts_sync; 2492 | backend_sync_class->modify_contacts_sync = book_backend_decsync_modify_contacts_sync; 2493 | backend_sync_class->remove_contacts_sync = book_backend_decsync_remove_contacts_sync; 2494 | backend_sync_class->get_contact_sync = book_backend_decsync_get_contact_sync; 2495 | backend_sync_class->get_contact_list_sync = book_backend_decsync_get_contact_list_sync; 2496 | backend_sync_class->get_contact_list_uids_sync = book_backend_decsync_get_contact_list_uids_sync; 2497 | backend_sync_class->contains_email_sync = book_backend_decsync_contains_email_sync; 2498 | 2499 | backend_class = E_BOOK_BACKEND_CLASS (class); 2500 | backend_class->impl_get_backend_property = book_backend_decsync_get_backend_property; 2501 | backend_class->impl_start_view = book_backend_decsync_start_view; 2502 | backend_class->impl_stop_view = book_backend_decsync_stop_view; 2503 | backend_class->impl_get_direct_book = book_backend_decsync_get_direct_book; 2504 | backend_class->impl_configure_direct = book_backend_decsync_configure_direct; 2505 | backend_class->impl_set_locale = book_backend_decsync_set_locale; 2506 | backend_class->impl_dup_locale = book_backend_decsync_dup_locale; 2507 | backend_class->impl_create_cursor = book_backend_decsync_create_cursor; 2508 | backend_class->impl_delete_cursor = book_backend_decsync_delete_cursor; 2509 | backend_class->impl_refresh = book_backend_decsync_refresh; 2510 | 2511 | E_TYPE_SOURCE_DECSYNC; 2512 | } 2513 | 2514 | static void 2515 | e_book_backend_decsync_initable_init (GInitableIface *iface) 2516 | { 2517 | iface->init = book_backend_decsync_initable_init; 2518 | } 2519 | 2520 | static void 2521 | e_book_backend_decsync_init (EBookBackendDecsync *backend) 2522 | { 2523 | backend->priv = e_book_backend_decsync_get_instance_private (backend); 2524 | 2525 | g_rw_lock_init (&(backend->priv->lock)); 2526 | } 2527 | 2528 | -------------------------------------------------------------------------------- /src/backends/addressbook/e-book-backend-decsync.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ 2 | 3 | /* e-book-backend-decsync.h - DecSync contact backend. 4 | * Based on the file contact backend. 5 | * 6 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 7 | * Copyright (C) 2012 Intel Corporation 8 | * Copyright (C) 2018 Aldo Gunsing 9 | * 10 | * This library is free software: you can redistribute it and/or modify it 11 | * under the terms of the GNU Lesser General Public License as published by 12 | * the Free Software Foundation. 13 | * 14 | * This library is distributed in the hope that it will be useful, but 15 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 17 | * for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public License 20 | * along with this library. If not, see . 21 | * 22 | * Authors: Nat Friedman 23 | * Chris Toshok 24 | * Hans Petter Jansson 25 | * Tristan Van Berkom 26 | */ 27 | 28 | #ifndef E_BOOK_BACKEND_DECSYNC_H 29 | #define E_BOOK_BACKEND_DECSYNC_H 30 | 31 | #include 32 | 33 | /* Standard GObject macros */ 34 | #define E_TYPE_BOOK_BACKEND_DECSYNC \ 35 | (e_book_backend_decsync_get_type ()) 36 | #define E_BOOK_BACKEND_DECSYNC(obj) \ 37 | (G_TYPE_CHECK_INSTANCE_CAST \ 38 | ((obj), E_TYPE_BOOK_BACKEND_DECSYNC, EBookBackendDecsync)) 39 | #define E_BOOK_BACKEND_DECSYNC_CLASS(cls) \ 40 | (G_TYPE_CHECK_CLASS_CAST \ 41 | ((cls), E_TYPE_BOOK_BACKEND_DECSYNC, EBookBackendDecsyncClass)) 42 | #define E_IS_BOOK_BACKEND_DECSYNC(obj) \ 43 | (G_TYPE_CHECK_INSTANCE_TYPE \ 44 | ((obj), E_TYPE_BOOK_BACKEND_DECSYNC)) 45 | #define E_IS_BOOK_BACKEND_DECSYNC_CLASS(cls) \ 46 | (G_TYPE_CHECK_CLASS_TYPE \ 47 | ((cls), E_TYPE_BOOK_BACKEND_DECSYNC)) 48 | #define E_BOOK_BACKEND_DECSYNC_GET_CLASS(obj) \ 49 | (G_TYPE_INSTANCE_GET_CLASS \ 50 | ((obj), E_TYPE_BOOK_BACKEND_DECSYNC, EBookBackendDecsyncClass)) 51 | 52 | G_BEGIN_DECLS 53 | 54 | typedef struct _EBookBackendDecsync EBookBackendDecsync; 55 | typedef struct _EBookBackendDecsyncClass EBookBackendDecsyncClass; 56 | typedef struct _EBookBackendDecsyncPrivate EBookBackendDecsyncPrivate; 57 | 58 | struct _EBookBackendDecsync { 59 | EBookBackendSync parent; 60 | EBookBackendDecsyncPrivate *priv; 61 | }; 62 | 63 | struct _EBookBackendDecsyncClass { 64 | EBookBackendSyncClass parent_class; 65 | }; 66 | 67 | GType e_book_backend_decsync_get_type (void); 68 | 69 | G_END_DECLS 70 | 71 | #endif /* E_BOOK_BACKEND_DECSYNC_H */ 72 | 73 | -------------------------------------------------------------------------------- /src/backends/addressbook/meson.build: -------------------------------------------------------------------------------- 1 | shared_library( 2 | 'ebookbackenddecsync', 3 | [ 4 | 'e-book-backend-decsync.c', 5 | 'e-book-backend-decsync.h', 6 | 'e-book-backend-decsync-factory.c', 7 | '../../e-source/e-source-decsync.c', 8 | '../../e-source/e-source-decsync.h' 9 | ], 10 | dependencies: [ 11 | json_glib, 12 | libdecsync, 13 | libedatabook 14 | ], 15 | c_args: [ 16 | '-DBACKENDDIR="' + ebook_backenddir + '"' 17 | ], 18 | install_mode: 'rw-r--r--', 19 | install: true, 20 | install_dir: ebook_backenddir, 21 | include_directories: include_directories(['..', '../..', '../../..',]) 22 | ) 23 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-events.c: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar decsync backend. 2 | * Based on the iCalendar file backend. 3 | * 4 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 5 | * Copyright (C) 2018 Aldo Gunsing 6 | * 7 | * This library is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation. 10 | * 11 | * This library is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 14 | * for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | * 19 | * Authors: Federico Mena-Quintero 20 | * Rodrigo Moya 21 | */ 22 | 23 | #include "evolution-decsync-config.h" 24 | 25 | #include "e-cal-backend-decsync-events.h" 26 | 27 | G_DEFINE_TYPE ( 28 | ECalBackendDecsyncEvents, 29 | e_cal_backend_decsync_events, 30 | E_TYPE_CAL_BACKEND_DECSYNC) 31 | 32 | static void 33 | e_cal_backend_decsync_events_class_init (ECalBackendDecsyncEventsClass *class) 34 | { 35 | } 36 | 37 | static void 38 | e_cal_backend_decsync_events_init (ECalBackendDecsyncEvents *cbfile) 39 | { 40 | e_cal_backend_decsync_set_file_name ( 41 | E_CAL_BACKEND_DECSYNC (cbfile), "calendar.ics"); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-events.h: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar decsync backend. 2 | * Based on the iCalendar file backend. 3 | * 4 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 5 | * Copyright (C) 2018 Aldo Gunsing 6 | * 7 | * This library is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation. 10 | * 11 | * This library is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 14 | * for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | * 19 | * Authors: Federico Mena-Quintero 20 | */ 21 | 22 | #ifndef E_CAL_BACKEND_DECSYNC_EVENTS_H 23 | #define E_CAL_BACKEND_DECSYNC_EVENTS_H 24 | 25 | #include "e-cal-backend-decsync.h" 26 | 27 | /* Standard GObject macros */ 28 | #define E_TYPE_CAL_BACKEND_DECSYNC_EVENTS \ 29 | (e_cal_backend_decsync_events_get_type ()) 30 | #define E_CAL_BACKEND_DECSYNC_EVENTS(obj) \ 31 | (G_TYPE_CHECK_INSTANCE_CAST \ 32 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_EVENTS, ECalBackendDecsyncEvents)) 33 | #define E_CAL_BACKEND_DECSYNC_EVENTS_CLASS(cls) \ 34 | (G_TYPE_CHECK_CLASS_CAST \ 35 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_EVENTS, ECalBackendDecsyncEventsClass)) 36 | #define E_IS_CAL_BACKEND_DECSYNC_EVENTS(obj) \ 37 | (G_TYPE_CHECK_INSTANCE_TYPE \ 38 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_EVENTS)) 39 | #define E_IS_CAL_BACKEND_DECSYNC_EVENTS_CLASS(cls) \ 40 | (G_TYPE_CHECK_CLASS_TYPE \ 41 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_EVENTS)) 42 | #define E_CAL_BACKEND_DECSYNC_EVENTS_GET_CLASS(obj) \ 43 | (G_TYPE_INSTANCE_GET_CLASS \ 44 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_EVENTS, ECalBackendDecsyncEventsClass)) 45 | 46 | G_BEGIN_DECLS 47 | 48 | typedef ECalBackendDecsync ECalBackendDecsyncEvents; 49 | typedef ECalBackendDecsyncClass ECalBackendDecsyncEventsClass; 50 | 51 | GType e_cal_backend_decsync_events_get_type (void); 52 | 53 | G_END_DECLS 54 | 55 | #endif /* E_CAL_BACKEND_DECSYNC_EVENTS_H */ 56 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-factory.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ 2 | 3 | /* Evolution calendar - iCalendar decsync backend factory. 4 | * Based on the iCalendar file backend factory. 5 | * 6 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 7 | * Copyright (C) 2018 Aldo Gunsing 8 | * 9 | * This library is free software: you can redistribute it and/or modify it 10 | * under the terms of the GNU Lesser General Public License as published by 11 | * the Free Software Foundation. 12 | * 13 | * This library is distributed in the hope that it will be useful, but 14 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 15 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 16 | * for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library. If not, see . 20 | * 21 | * Authors: Chris Toshok (toshok@ximian.com) 22 | */ 23 | 24 | #include "evolution-decsync-config.h" 25 | 26 | #include "e-cal-backend-decsync-events.h" 27 | #include "e-cal-backend-decsync-journal.h" 28 | #include "e-cal-backend-decsync-todos.h" 29 | 30 | #define FACTORY_NAME "decsync" 31 | 32 | typedef ECalBackendFactory ECalBackendDecsyncEventsFactory; 33 | typedef ECalBackendFactoryClass ECalBackendDecsyncEventsFactoryClass; 34 | 35 | typedef ECalBackendFactory ECalBackendDecsyncJournalFactory; 36 | typedef ECalBackendFactoryClass ECalBackendDecsyncJournalFactoryClass; 37 | 38 | typedef ECalBackendFactory ECalBackendDecsyncTodosFactory; 39 | typedef ECalBackendFactoryClass ECalBackendDecsyncTodosFactoryClass; 40 | 41 | static EModule *e_module; 42 | 43 | /* Module Entry Points */ 44 | void e_module_load (GTypeModule *type_module); 45 | void e_module_unload (GTypeModule *type_module); 46 | 47 | /* Forward Declarations */ 48 | GType e_cal_backend_decsync_events_factory_get_type (void); 49 | GType e_cal_backend_decsync_journal_factory_get_type (void); 50 | GType e_cal_backend_decsync_todos_factory_get_type (void); 51 | 52 | G_DEFINE_DYNAMIC_TYPE ( 53 | ECalBackendDecsyncEventsFactory, 54 | e_cal_backend_decsync_events_factory, 55 | E_TYPE_CAL_BACKEND_FACTORY) 56 | 57 | G_DEFINE_DYNAMIC_TYPE ( 58 | ECalBackendDecsyncJournalFactory, 59 | e_cal_backend_decsync_journal_factory, 60 | E_TYPE_CAL_BACKEND_FACTORY) 61 | 62 | G_DEFINE_DYNAMIC_TYPE ( 63 | ECalBackendDecsyncTodosFactory, 64 | e_cal_backend_decsync_todos_factory, 65 | E_TYPE_CAL_BACKEND_FACTORY) 66 | 67 | static void 68 | e_cal_backend_decsync_events_factory_class_init (ECalBackendFactoryClass *class) 69 | { 70 | EBackendFactoryClass *backend_factory_class; 71 | 72 | backend_factory_class = E_BACKEND_FACTORY_CLASS (class); 73 | backend_factory_class->e_module = e_module; 74 | backend_factory_class->share_subprocess = TRUE; 75 | 76 | class->factory_name = FACTORY_NAME; 77 | class->component_kind = I_CAL_VEVENT_COMPONENT; 78 | class->backend_type = E_TYPE_CAL_BACKEND_DECSYNC_EVENTS; 79 | } 80 | 81 | static void 82 | e_cal_backend_decsync_events_factory_class_finalize (ECalBackendFactoryClass *class) 83 | { 84 | } 85 | 86 | static void 87 | e_cal_backend_decsync_events_factory_init (ECalBackendFactory *factory) 88 | { 89 | } 90 | 91 | static void 92 | e_cal_backend_decsync_journal_factory_class_init (ECalBackendFactoryClass *class) 93 | { 94 | EBackendFactoryClass *backend_factory_class; 95 | 96 | backend_factory_class = E_BACKEND_FACTORY_CLASS (class); 97 | backend_factory_class->e_module = e_module; 98 | backend_factory_class->share_subprocess = TRUE; 99 | 100 | class->factory_name = FACTORY_NAME; 101 | class->component_kind = I_CAL_VJOURNAL_COMPONENT; 102 | class->backend_type = E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL; 103 | } 104 | 105 | static void 106 | e_cal_backend_decsync_journal_factory_class_finalize (ECalBackendFactoryClass *class) 107 | { 108 | } 109 | 110 | static void 111 | e_cal_backend_decsync_journal_factory_init (ECalBackendFactory *factory) 112 | { 113 | } 114 | 115 | static void 116 | e_cal_backend_decsync_todos_factory_class_init (ECalBackendFactoryClass *class) 117 | { 118 | EBackendFactoryClass *backend_factory_class; 119 | 120 | backend_factory_class = E_BACKEND_FACTORY_CLASS (class); 121 | backend_factory_class->e_module = e_module; 122 | backend_factory_class->share_subprocess = TRUE; 123 | 124 | class->factory_name = FACTORY_NAME; 125 | class->component_kind = I_CAL_VTODO_COMPONENT; 126 | class->backend_type = E_TYPE_CAL_BACKEND_DECSYNC_TODOS; 127 | } 128 | 129 | static void 130 | e_cal_backend_decsync_todos_factory_class_finalize (ECalBackendFactoryClass *class) 131 | { 132 | } 133 | 134 | static void 135 | e_cal_backend_decsync_todos_factory_init (ECalBackendFactory *factory) 136 | { 137 | } 138 | 139 | G_MODULE_EXPORT void 140 | e_module_load (GTypeModule *type_module) 141 | { 142 | e_module = E_MODULE (type_module); 143 | 144 | e_cal_backend_decsync_events_factory_register_type (type_module); 145 | e_cal_backend_decsync_journal_factory_register_type (type_module); 146 | e_cal_backend_decsync_todos_factory_register_type (type_module); 147 | } 148 | 149 | G_MODULE_EXPORT void 150 | e_module_unload (GTypeModule *type_module) 151 | { 152 | e_module = NULL; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-journal.c: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar file backend for tasks 2 | * 3 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 4 | * Copyright (C) 2020 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | * 18 | * Authors: Rodrigo Moya 19 | */ 20 | 21 | #include "e-cal-backend-decsync-journal.h" 22 | 23 | G_DEFINE_TYPE ( 24 | ECalBackendDecsyncJournal, 25 | e_cal_backend_decsync_journal, 26 | E_TYPE_CAL_BACKEND_DECSYNC) 27 | 28 | static void 29 | e_cal_backend_decsync_journal_class_init (ECalBackendDecsyncJournalClass *class) 30 | { 31 | } 32 | 33 | static void 34 | e_cal_backend_decsync_journal_init (ECalBackendDecsyncJournal *cbfile) 35 | { 36 | e_cal_backend_decsync_set_file_name ( 37 | E_CAL_BACKEND_DECSYNC (cbfile), "journal.ics"); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-journal.h: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar file backend 2 | * 3 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 4 | * Copyright (C) 2020 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | * 18 | * Authors: Rodrigo Moya 19 | */ 20 | 21 | #ifndef E_CAL_BACKEND_DECSYNC_JOURNAL_H 22 | #define E_CAL_BACKEND_DECSYNC_JOURNAL_H 23 | 24 | #include "e-cal-backend-decsync.h" 25 | 26 | /* Standard GObject macros */ 27 | #define E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL \ 28 | (e_cal_backend_decsync_journal_get_type ()) 29 | #define E_CAL_BACKEND_DECSYNC_JOURNAL(obj) \ 30 | (G_TYPE_CHECK_INSTANCE_CAST \ 31 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL, ECalBackendDecsyncJournal)) 32 | #define E_CAL_BACKEND_DECSYNC_JOURNAL_CLASS(cls) \ 33 | (G_TYPE_CHECK_CLASS_CAST \ 34 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL, ECalBackendDecsyncJournalClass)) 35 | #define E_IS_CAL_BACKEND_DECSYNC_JOURNAL(obj) \ 36 | (G_TYPE_CHECK_INSTANCE_TYPE \ 37 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL)) 38 | #define E_IS_CAL_BACKEND_DECSYNC_JOURNAL_CLASS(cls) \ 39 | (G_TYPE_CHECK_CLASS_TYPE \ 40 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL)) 41 | #define E_CAL_BACKEND_DECSYNC_JOURNAL_GET_CLASS(obj) \ 42 | (G_TYPE_INSTANCE_GET_CLASS \ 43 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_JOURNAL, ECalBackendDecsyncJournalClass)) 44 | 45 | G_BEGIN_DECLS 46 | 47 | typedef ECalBackendDecsync ECalBackendDecsyncJournal; 48 | typedef ECalBackendDecsyncClass ECalBackendDecsyncJournalClass; 49 | 50 | GType e_cal_backend_decsync_journal_get_type (void); 51 | 52 | G_END_DECLS 53 | 54 | #endif /* E_CAL_BACKEND_DECSYNC_JOURNAL_H */ 55 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-todos.c: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar file backend for tasks 2 | * 3 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 4 | * Copyright (C) 2020 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | * 18 | * Authors: Federico Mena-Quintero 19 | * Rodrigo Moya 20 | */ 21 | 22 | #include "evolution-decsync-config.h" 23 | 24 | #include "e-cal-backend-decsync-todos.h" 25 | 26 | G_DEFINE_TYPE ( 27 | ECalBackendDecsyncTodos, 28 | e_cal_backend_decsync_todos, 29 | E_TYPE_CAL_BACKEND_DECSYNC) 30 | 31 | static void 32 | e_cal_backend_decsync_todos_class_init (ECalBackendDecsyncTodosClass *class) 33 | { 34 | } 35 | 36 | static void 37 | e_cal_backend_decsync_todos_init (ECalBackendDecsyncTodos *cbfile) 38 | { 39 | e_cal_backend_decsync_set_file_name ( 40 | E_CAL_BACKEND_DECSYNC (cbfile), "tasks.ics"); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync-todos.h: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar file backend 2 | * 3 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 4 | * Copyright (C) 2020 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | * 18 | * Authors: Federico Mena-Quintero 19 | */ 20 | 21 | #ifndef E_CAL_BACKEND_DECSYNC_TODOS_H 22 | #define E_CAL_BACKEND_DECSYNC_TODOS_H 23 | 24 | #include "e-cal-backend-decsync.h" 25 | 26 | /* Standard GObject macros */ 27 | #define E_TYPE_CAL_BACKEND_DECSYNC_TODOS \ 28 | (e_cal_backend_decsync_todos_get_type ()) 29 | #define E_CAL_BACKEND_DECSYNC_TODOS(obj) \ 30 | (G_TYPE_CHECK_INSTANCE_CAST \ 31 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_TODOS, ECalBackendDecsyncTodos)) 32 | #define E_CAL_BACKEND_DECSYNC_TODOS_CLASS(cls) \ 33 | (G_TYPE_CHECK_CLASS_CAST \ 34 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_TODOS, ECalBackendDecsyncTodosClass)) 35 | #define E_IS_CAL_BACKEND_DECSYNC_TODOS(obj) \ 36 | (G_TYPE_CHECK_INSTANCE_TYPE \ 37 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_TODOS)) 38 | #define E_IS_CAL_BACKEND_DECSYNC_TODOS_CLASS(cls) \ 39 | (G_TYPE_CHECK_CLASS_TYPE \ 40 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC_TODOS)) 41 | #define E_CAL_BACKEND_DECSYNC_TODOS_GET_CLASS(obj) \ 42 | (G_TYPE_INSTANCE_GET_CLASS \ 43 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC_TODOS, ECalBackendDecsyncTodosClass)) 44 | 45 | G_BEGIN_DECLS 46 | 47 | typedef ECalBackendDecsync ECalBackendDecsyncTodos; 48 | typedef ECalBackendDecsyncClass ECalBackendDecsyncTodosClass; 49 | 50 | GType e_cal_backend_decsync_todos_get_type (void); 51 | 52 | G_END_DECLS 53 | 54 | #endif /* E_CAL_BACKEND_DECSYNC_TODOS_H */ 55 | -------------------------------------------------------------------------------- /src/backends/calendar/e-cal-backend-decsync.h: -------------------------------------------------------------------------------- 1 | /* Evolution calendar - iCalendar decsync backend. 2 | * Based on the iCalendar file backend. 3 | * 4 | * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) 5 | * Copyright (C) 2018 Aldo Gunsing 6 | * 7 | * This library is free software: you can redistribute it and/or modify it 8 | * under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation. 10 | * 11 | * This library is distributed in the hope that it will be useful, but 12 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 14 | * for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | * 19 | * Authors: Federico Mena-Quintero 20 | */ 21 | 22 | #ifndef E_CAL_BACKEND_DECSYNC_H 23 | #define E_CAL_BACKEND_DECSYNC_H 24 | 25 | #include 26 | 27 | /* Standard GObject macros */ 28 | #define E_TYPE_CAL_BACKEND_DECSYNC \ 29 | (e_cal_backend_decsync_get_type ()) 30 | #define E_CAL_BACKEND_DECSYNC(obj) \ 31 | (G_TYPE_CHECK_INSTANCE_CAST \ 32 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC, ECalBackendDecsync)) 33 | #define E_CAL_BACKEND_DECSYNC_CLASS(cls) \ 34 | (G_TYPE_CHECK_CLASS_CAST \ 35 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC, ECalBackendDecsyncClass)) 36 | #define E_IS_CAL_BACKEND_DECSYNC(obj) \ 37 | (G_TYPE_CHECK_INSTANCE_TYPE \ 38 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC)) 39 | #define E_IS_CAL_BACKEND_DECSYNC_CLASS(cls) \ 40 | (G_TYPE_CHECK_CLASS_TYPE \ 41 | ((cls), E_TYPE_CAL_BACKEND_DECSYNC)) 42 | #define E_CAL_BACKEND_DECSYNC_GET_CLASS(obj) \ 43 | (G_TYPE_INSTANCE_GET_CLASS \ 44 | ((obj), E_TYPE_CAL_BACKEND_DECSYNC, ECalBackendDecsyncClass)) 45 | 46 | G_BEGIN_DECLS 47 | 48 | typedef struct _ECalBackendDecsync ECalBackendDecsync; 49 | typedef struct _ECalBackendDecsyncClass ECalBackendDecsyncClass; 50 | typedef struct _ECalBackendDecsyncPrivate ECalBackendDecsyncPrivate; 51 | 52 | struct _ECalBackendDecsync { 53 | ECalBackendSync parent; 54 | ECalBackendDecsyncPrivate *priv; 55 | }; 56 | 57 | struct _ECalBackendDecsyncClass { 58 | ECalBackendSyncClass parent_class; 59 | }; 60 | 61 | GType e_cal_backend_decsync_get_type (void); 62 | const gchar * e_cal_backend_decsync_get_file_name 63 | (ECalBackendDecsync *cbfile); 64 | void e_cal_backend_decsync_set_file_name 65 | (ECalBackendDecsync *cbfile, 66 | const gchar *file_name); 67 | void e_cal_backend_decsync_reload (ECalBackendDecsync *cbfile, 68 | GError **error); 69 | 70 | G_END_DECLS 71 | 72 | #endif /* E_CAL_BACKEND_DECSYNC_H */ 73 | -------------------------------------------------------------------------------- /src/backends/calendar/meson.build: -------------------------------------------------------------------------------- 1 | shared_library( 2 | 'ecalbackenddecsync', 3 | [ 4 | 'e-cal-backend-decsync.c', 5 | 'e-cal-backend-decsync.h', 6 | 'e-cal-backend-decsync-events.c', 7 | 'e-cal-backend-decsync-events.h', 8 | 'e-cal-backend-decsync-journal.c', 9 | 'e-cal-backend-decsync-journal.h', 10 | 'e-cal-backend-decsync-todos.c', 11 | 'e-cal-backend-decsync-todos.h', 12 | 'e-cal-backend-decsync-factory.c', 13 | '../../e-source/e-source-decsync.c', 14 | '../../e-source/e-source-decsync.h' 15 | ], 16 | dependencies: [ 17 | json_glib, 18 | libdecsync, 19 | libedatacal 20 | ], 21 | install_mode: 'rw-r--r--', 22 | install: true, 23 | install_dir: ecal_backenddir, 24 | include_directories: include_directories(['..', '../..', '../../..']) 25 | ) 26 | -------------------------------------------------------------------------------- /src/backends/meson.build: -------------------------------------------------------------------------------- 1 | subdir('addressbook') 2 | subdir('calendar') 3 | -------------------------------------------------------------------------------- /src/e-source/decsync.source: -------------------------------------------------------------------------------- 1 | [Data Source] 2 | DisplayName=DecSync 3 | Enabled=true 4 | Parent= 5 | -------------------------------------------------------------------------------- /src/e-source/e-source-decsync.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - e-source-decsync.c 3 | * 4 | * Copyright (C) 2018 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | */ 18 | 19 | #include "evolution-decsync-config.h" 20 | 21 | #include "e-source-decsync.h" 22 | 23 | struct _ESourceDecsyncPrivate { 24 | gchar *decsync_dir; 25 | gchar *collection; 26 | gchar *appid; 27 | }; 28 | 29 | enum { 30 | PROP_0, 31 | PROP_DECSYNC_DIR, 32 | PROP_COLLECTION, 33 | PROP_APPID 34 | }; 35 | 36 | G_DEFINE_TYPE_WITH_CODE ( 37 | ESourceDecsync, 38 | e_source_decsync, 39 | E_TYPE_SOURCE_EXTENSION, 40 | G_ADD_PRIVATE (ESourceDecsync)) 41 | 42 | static void 43 | source_decsync_set_property (GObject *object, 44 | guint property_id, 45 | const GValue *value, 46 | GParamSpec *pspec) 47 | { 48 | switch (property_id) { 49 | case PROP_DECSYNC_DIR: 50 | e_source_decsync_set_decsync_dir ( 51 | E_SOURCE_DECSYNC (object), 52 | g_value_get_string (value)); 53 | return; 54 | 55 | case PROP_COLLECTION: 56 | e_source_decsync_set_collection ( 57 | E_SOURCE_DECSYNC (object), 58 | g_value_get_string (value)); 59 | return; 60 | 61 | case PROP_APPID: 62 | e_source_decsync_set_appid ( 63 | E_SOURCE_DECSYNC (object), 64 | g_value_get_string (value)); 65 | return; 66 | } 67 | 68 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 69 | } 70 | 71 | static void 72 | source_decsync_get_property (GObject *object, 73 | guint property_id, 74 | GValue *value, 75 | GParamSpec *pspec) 76 | { 77 | switch (property_id) { 78 | case PROP_DECSYNC_DIR: 79 | g_value_take_string ( 80 | value, 81 | e_source_decsync_dup_decsync_dir ( 82 | E_SOURCE_DECSYNC (object))); 83 | return; 84 | 85 | case PROP_COLLECTION: 86 | g_value_take_string ( 87 | value, 88 | e_source_decsync_dup_collection ( 89 | E_SOURCE_DECSYNC (object))); 90 | return; 91 | 92 | case PROP_APPID: 93 | g_value_take_string ( 94 | value, 95 | e_source_decsync_dup_appid ( 96 | E_SOURCE_DECSYNC (object))); 97 | return; 98 | } 99 | 100 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); 101 | } 102 | 103 | static void 104 | source_decsync_finalize (GObject *object) 105 | { 106 | ESourceDecsyncPrivate *priv; 107 | 108 | priv = E_SOURCE_DECSYNC (object)->priv; 109 | 110 | g_free (priv->decsync_dir); 111 | g_free (priv->collection); 112 | g_free (priv->appid); 113 | 114 | /* Chain up to parent's finalize() method. */ 115 | G_OBJECT_CLASS (e_source_decsync_parent_class)->finalize (object); 116 | } 117 | 118 | static void 119 | e_source_decsync_class_init (ESourceDecsyncClass *class) 120 | { 121 | GObjectClass *object_class; 122 | ESourceExtensionClass *extension_class; 123 | 124 | object_class = G_OBJECT_CLASS (class); 125 | object_class->set_property = source_decsync_set_property; 126 | object_class->get_property = source_decsync_get_property; 127 | object_class->finalize = source_decsync_finalize; 128 | 129 | extension_class = E_SOURCE_EXTENSION_CLASS (class); 130 | extension_class->name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 131 | 132 | g_object_class_install_property ( 133 | object_class, 134 | PROP_DECSYNC_DIR, 135 | g_param_spec_string ( 136 | "decsync-dir", 137 | "Decsync Dir", 138 | "DecSync directory", 139 | "", 140 | G_PARAM_READWRITE | 141 | G_PARAM_CONSTRUCT | 142 | E_SOURCE_PARAM_SETTING)); 143 | 144 | g_object_class_install_property ( 145 | object_class, 146 | PROP_COLLECTION, 147 | g_param_spec_string ( 148 | "collection", 149 | "Collection", 150 | "Collection", 151 | "", 152 | G_PARAM_READWRITE | 153 | G_PARAM_CONSTRUCT | 154 | E_SOURCE_PARAM_SETTING)); 155 | 156 | g_object_class_install_property ( 157 | object_class, 158 | PROP_APPID, 159 | g_param_spec_string ( 160 | "app-id", 161 | "AppId", 162 | "AppId", 163 | "", 164 | G_PARAM_READWRITE | 165 | G_PARAM_CONSTRUCT | 166 | E_SOURCE_PARAM_SETTING)); 167 | } 168 | 169 | static void 170 | e_source_decsync_init (ESourceDecsync *extension) 171 | { 172 | extension->priv = e_source_decsync_get_instance_private (extension); 173 | } 174 | 175 | const gchar * 176 | e_source_decsync_get_decsync_dir (ESourceDecsync *extension) 177 | { 178 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 179 | 180 | return extension->priv->decsync_dir; 181 | } 182 | 183 | gchar * 184 | e_source_decsync_dup_decsync_dir (ESourceDecsync *extension) 185 | { 186 | const gchar *protected; 187 | gchar *duplicate; 188 | 189 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 190 | 191 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 192 | 193 | protected = e_source_decsync_get_decsync_dir (extension); 194 | duplicate = g_strdup (protected); 195 | 196 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 197 | 198 | return duplicate; 199 | } 200 | 201 | void 202 | e_source_decsync_set_decsync_dir (ESourceDecsync *extension, const gchar *decsync_dir) 203 | { 204 | g_return_if_fail (E_IS_SOURCE_DECSYNC (extension)); 205 | 206 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 207 | 208 | if (g_strcmp0 (extension->priv->decsync_dir, decsync_dir) == 0) { 209 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 210 | return; 211 | } 212 | 213 | g_free (extension->priv->decsync_dir); 214 | extension->priv->decsync_dir = g_strdup (decsync_dir); 215 | 216 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 217 | 218 | g_object_notify (G_OBJECT (extension), "decsync-dir"); 219 | } 220 | 221 | const gchar * 222 | e_source_decsync_get_collection (ESourceDecsync *extension) 223 | { 224 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 225 | 226 | return extension->priv->collection; 227 | } 228 | 229 | gchar * 230 | e_source_decsync_dup_collection (ESourceDecsync *extension) 231 | { 232 | const gchar *protected; 233 | gchar *duplicate; 234 | 235 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 236 | 237 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 238 | 239 | protected = e_source_decsync_get_collection (extension); 240 | duplicate = g_strdup (protected); 241 | 242 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 243 | 244 | return duplicate; 245 | } 246 | 247 | void 248 | e_source_decsync_set_collection (ESourceDecsync *extension, const gchar *collection) 249 | { 250 | g_return_if_fail (E_IS_SOURCE_DECSYNC (extension)); 251 | 252 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 253 | 254 | if (g_strcmp0 (extension->priv->collection, collection) == 0) { 255 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 256 | return; 257 | } 258 | 259 | g_free (extension->priv->collection); 260 | extension->priv->collection = g_strdup (collection); 261 | 262 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 263 | 264 | g_object_notify (G_OBJECT (extension), "decsync-dir"); 265 | } 266 | 267 | const gchar * 268 | e_source_decsync_get_appid (ESourceDecsync *extension) 269 | { 270 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 271 | 272 | return extension->priv->appid; 273 | } 274 | 275 | gchar * 276 | e_source_decsync_dup_appid (ESourceDecsync *extension) 277 | { 278 | const gchar *protected; 279 | gchar *duplicate; 280 | 281 | g_return_val_if_fail (E_IS_SOURCE_DECSYNC (extension), NULL); 282 | 283 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 284 | 285 | protected = e_source_decsync_get_appid (extension); 286 | duplicate = g_strdup (protected); 287 | 288 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 289 | 290 | return duplicate; 291 | } 292 | 293 | void 294 | e_source_decsync_set_appid (ESourceDecsync *extension, const gchar *appid) 295 | { 296 | g_return_if_fail (E_IS_SOURCE_DECSYNC (extension)); 297 | 298 | e_source_extension_property_lock (E_SOURCE_EXTENSION (extension)); 299 | 300 | if (g_strcmp0 (extension->priv->appid, appid) == 0) { 301 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 302 | return; 303 | } 304 | 305 | g_free (extension->priv->appid); 306 | extension->priv->appid = g_strdup (appid); 307 | 308 | e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension)); 309 | 310 | g_object_notify (G_OBJECT (extension), "app-id"); 311 | } 312 | -------------------------------------------------------------------------------- /src/e-source/e-source-decsync.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - e-source-decsync.h 3 | * 4 | * Copyright (C) 2018 Aldo Gunsing 5 | * 6 | * This library is free software: you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library. If not, see . 17 | */ 18 | 19 | #ifndef E_SOURCE_DECSYNC_H 20 | #define E_SOURCE_DECSYNC_H 21 | 22 | #include 23 | 24 | /* Standard GObject macros */ 25 | #define E_TYPE_SOURCE_DECSYNC \ 26 | (e_source_decsync_get_type ()) 27 | #define E_SOURCE_DECSYNC(obj) \ 28 | (G_TYPE_CHECK_INSTANCE_CAST \ 29 | ((obj), E_TYPE_SOURCE_DECSYNC, ESourceDecsync)) 30 | #define E_SOURCE_DECSYNC_CLASS(cls) \ 31 | (G_TYPE_CHECK_CLASS_CAST \ 32 | ((cls), E_TYPE_SOURCE_DECSYNC, ESourceDecsyncClass)) 33 | #define E_IS_SOURCE_DECSYNC(obj) \ 34 | (G_TYPE_CHECK_INSTANCE_TYPE \ 35 | ((obj), E_TYPE_SOURCE_DECSYNC)) 36 | #define E_IS_SOURCE_DECSYNC_CLASS(cls) \ 37 | (G_TYPE_CHECK_CLASS_TYPE \ 38 | ((cls), E_TYPE_SOURCE_DECSYNC)) 39 | #define E_SOURCE_DECSYNC_GET_CLASS(obj) \ 40 | (G_TYPE_INSTANCE_GET_CLASS \ 41 | ((obj), E_TYPE_SOURCE_DECSYNC, ESourceDecsyncClass)) 42 | 43 | /** 44 | * E_SOURCE_EXTENSION_DECSYNC_BACKEND: 45 | * 46 | * Pass this extension name to e_source_get_extension() to access 47 | * #ESourceDecsync. This is also used as a group name in key files. 48 | * 49 | * Since: 3.18 50 | **/ 51 | #define E_SOURCE_EXTENSION_DECSYNC_BACKEND "DecSync Backend" 52 | 53 | G_BEGIN_DECLS 54 | 55 | typedef struct _ESourceDecsync ESourceDecsync; 56 | typedef struct _ESourceDecsyncClass ESourceDecsyncClass; 57 | typedef struct _ESourceDecsyncPrivate ESourceDecsyncPrivate; 58 | 59 | struct _ESourceDecsync { 60 | /*< private >*/ 61 | ESourceExtension parent; 62 | ESourceDecsyncPrivate *priv; 63 | }; 64 | 65 | struct _ESourceDecsyncClass { 66 | ESourceExtensionClass parent_class; 67 | }; 68 | 69 | GType e_source_decsync_get_type (void); 70 | const gchar * e_source_decsync_get_decsync_dir (ESourceDecsync *extension); 71 | gchar * e_source_decsync_dup_decsync_dir (ESourceDecsync *extension); 72 | void e_source_decsync_set_decsync_dir (ESourceDecsync *extension, const gchar *decsync_dir); 73 | const gchar * e_source_decsync_get_collection (ESourceDecsync *extension); 74 | gchar * e_source_decsync_dup_collection (ESourceDecsync *extension); 75 | void e_source_decsync_set_collection (ESourceDecsync *extension, const gchar *collection); 76 | const gchar * e_source_decsync_get_appid (ESourceDecsync *extension); 77 | gchar * e_source_decsync_dup_appid (ESourceDecsync *extension); 78 | void e_source_decsync_set_appid (ESourceDecsync *extension, const gchar *appid); 79 | 80 | G_END_DECLS 81 | 82 | #endif /* E_SOURCE_DECSYNC_H */ 83 | -------------------------------------------------------------------------------- /src/e-source/meson.build: -------------------------------------------------------------------------------- 1 | install_data( 2 | [ 3 | 'decsync.source' 4 | ], 5 | install_dir: E_SOURCE_DIR 6 | ) 7 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('e-source') 2 | subdir('backends') 3 | subdir('modules') 4 | -------------------------------------------------------------------------------- /src/modules/book-config/meson.build: -------------------------------------------------------------------------------- 1 | shared_library( 2 | 'module-book-config-decsync', 3 | [ 4 | 'module-book-config-decsync.c', 5 | '../../e-source/e-source-decsync.c', 6 | '../../e-source/e-source-decsync.h', 7 | '../utils/decsync.c', 8 | '../utils/decsync.h' 9 | ], 10 | name_prefix: '', 11 | dependencies: [ 12 | json_glib, 13 | libdecsync, 14 | libedatabook, 15 | evolutionshell 16 | ], 17 | install_mode: 'rw-r--r--', 18 | install: true, 19 | install_dir: evo_moduledir, 20 | include_directories: include_directories(['..', '../..', '../../..']) 21 | ) 22 | -------------------------------------------------------------------------------- /src/modules/book-config/module-book-config-decsync.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - module-book-config-decsync.c 3 | * 4 | * This library is free software; you can redistribute it and/or modify it 5 | * under the terms of the GNU Lesser General Public License as published by 6 | * the Free Software Foundation. 7 | * 8 | * This library is distributed in the hope that it will be useful, but 9 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 10 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 11 | * for more details. 12 | * 13 | * You should have received a copy of the GNU Lesser General Public License 14 | * along with this library; if not, see . 15 | */ 16 | 17 | #include "evolution-decsync-config.h" 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | 28 | typedef ESourceConfigBackend EBookConfigDecsync; 29 | typedef ESourceConfigBackendClass EBookConfigDecsyncClass; 30 | 31 | /* Module Entry Points */ 32 | void e_module_load (GTypeModule *type_module); 33 | void e_module_unload (GTypeModule *type_module); 34 | 35 | /* Forward Declarations */ 36 | GType e_book_config_decsync_get_type (void); 37 | 38 | G_DEFINE_DYNAMIC_TYPE ( 39 | EBookConfigDecsync, 40 | e_book_config_decsync, 41 | E_TYPE_SOURCE_CONFIG_BACKEND) 42 | 43 | static void 44 | book_config_decsync_insert_widgets (ESourceConfigBackend *backend, ESource *scratch_source) 45 | { 46 | config_decsync_insert_widgets ("contacts", _("Address Book"), backend, scratch_source); 47 | } 48 | 49 | static void 50 | e_book_config_decsync_class_init (ESourceConfigBackendClass *class) 51 | { 52 | EExtensionClass *extension_class; 53 | 54 | config_decsync_add_source_file (); 55 | 56 | extension_class = E_EXTENSION_CLASS (class); 57 | extension_class->extensible_type = E_TYPE_BOOK_SOURCE_CONFIG; 58 | 59 | class->parent_uid = "decsync"; 60 | class->backend_name = "decsync"; 61 | class->insert_widgets = book_config_decsync_insert_widgets; 62 | class->check_complete = config_decsync_check_complete; 63 | class->commit_changes = config_decsync_commit_changes; 64 | 65 | E_TYPE_SOURCE_DECSYNC; 66 | } 67 | 68 | static void 69 | e_book_config_decsync_class_finalize (ESourceConfigBackendClass *class) 70 | { 71 | } 72 | 73 | static void 74 | e_book_config_decsync_init (ESourceConfigBackend *backend) 75 | { 76 | } 77 | 78 | G_MODULE_EXPORT void 79 | e_module_load (GTypeModule *type_module) 80 | { 81 | e_book_config_decsync_register_type (type_module); 82 | } 83 | 84 | G_MODULE_EXPORT void 85 | e_module_unload (GTypeModule *type_module) 86 | { 87 | } 88 | -------------------------------------------------------------------------------- /src/modules/cal-config/meson.build: -------------------------------------------------------------------------------- 1 | shared_library( 2 | 'module-cal-config-decsync', 3 | [ 4 | 'module-cal-config-decsync.c', 5 | '../../e-source/e-source-decsync.c', 6 | '../../e-source/e-source-decsync.h', 7 | '../utils/decsync.c', 8 | '../utils/decsync.h' 9 | ], 10 | name_prefix: '', 11 | dependencies: [ 12 | json_glib, 13 | libdecsync, 14 | libedatacal, 15 | evolutionshell 16 | ], 17 | install_mode: 'rw-r--r--', 18 | install: true, 19 | install_dir: evo_moduledir, 20 | include_directories: include_directories(['..', '../..', '../../..']) 21 | ) 22 | -------------------------------------------------------------------------------- /src/modules/cal-config/module-cal-config-decsync.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - module-cal-config-decsync.c 3 | * 4 | * Copyright (C) 2018 Aldo Gunsing 5 | * 6 | * This library is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library; if not, see . 17 | */ 18 | 19 | #include "evolution-decsync-config.h" 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | 30 | typedef ESourceConfigBackend ECalConfigDecsync; 31 | typedef ESourceConfigBackendClass ECalConfigDecsyncClass; 32 | 33 | /* Module Entry Points */ 34 | void e_module_load (GTypeModule *type_module); 35 | void e_module_unload (GTypeModule *type_module); 36 | 37 | /* Forward Declarations */ 38 | GType e_cal_config_decsync_get_type (void); 39 | 40 | G_DEFINE_DYNAMIC_TYPE ( 41 | ECalConfigDecsync, 42 | e_cal_config_decsync, 43 | E_TYPE_SOURCE_CONFIG_BACKEND) 44 | 45 | static void 46 | cal_config_decsync_insert_widgets (ESourceConfigBackend *backend, ESource *scratch_source) 47 | { 48 | ESourceConfig *config; 49 | ECalSourceConfig *cal_config; 50 | ECalClientSourceType source_type; 51 | 52 | config = e_source_config_backend_get_config (backend); 53 | 54 | cal_config = E_CAL_SOURCE_CONFIG (config); 55 | source_type = e_cal_source_config_get_source_type (cal_config); 56 | switch (source_type) 57 | { 58 | case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 59 | config_decsync_insert_widgets ("calendars", _("Calendar"), backend, scratch_source); 60 | break; 61 | case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 62 | config_decsync_insert_widgets ("tasks", _("Task List"), backend, scratch_source); 63 | break; 64 | case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 65 | config_decsync_insert_widgets ("memos", _("Memo List"), backend, scratch_source); 66 | break; 67 | case E_CAL_CLIENT_SOURCE_TYPE_LAST: 68 | // Do nothing, artificial value 69 | break; 70 | } 71 | } 72 | 73 | static void 74 | e_cal_config_decsync_class_init (ESourceConfigBackendClass *class) 75 | { 76 | EExtensionClass *extension_class; 77 | 78 | config_decsync_add_source_file (); 79 | 80 | extension_class = E_EXTENSION_CLASS (class); 81 | extension_class->extensible_type = E_TYPE_CAL_SOURCE_CONFIG; 82 | 83 | class->parent_uid = "decsync"; 84 | class->backend_name = "decsync"; 85 | class->insert_widgets = cal_config_decsync_insert_widgets; 86 | class->check_complete = config_decsync_check_complete; 87 | class->commit_changes = config_decsync_commit_changes; 88 | 89 | E_TYPE_SOURCE_DECSYNC; 90 | } 91 | 92 | static void 93 | e_cal_config_decsync_class_finalize (ESourceConfigBackendClass *class) 94 | { 95 | } 96 | 97 | static void 98 | e_cal_config_decsync_init (ESourceConfigBackend *backend) 99 | { 100 | } 101 | 102 | G_MODULE_EXPORT void 103 | e_module_load (GTypeModule *type_module) 104 | { 105 | e_cal_config_decsync_register_type (type_module); 106 | } 107 | 108 | G_MODULE_EXPORT void 109 | e_module_unload (GTypeModule *type_module) 110 | { 111 | } 112 | -------------------------------------------------------------------------------- /src/modules/meson.build: -------------------------------------------------------------------------------- 1 | subdir('book-config') 2 | subdir('cal-config') 3 | -------------------------------------------------------------------------------- /src/modules/utils/decsync.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - decsync.c 3 | * 4 | * Copyright (C) 2018 Aldo Gunsing 5 | * 6 | * This library is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library; if not, see . 17 | */ 18 | 19 | #include "decsync.h" 20 | #include 21 | #include 22 | 23 | typedef struct _Context Context; 24 | 25 | struct _Context { 26 | ESourceConfigBackend *backend; /* not referenced */ 27 | ESource *scratch_source; /* not referenced */ 28 | gchar *orig_color; 29 | const gchar *sync_type; /* not referenced */ 30 | const gchar *sync_type_title; /* not referenced */ 31 | GtkButton *decsync_dir_button; 32 | GtkComboBoxText *collection_combo_box; 33 | GtkButton *collection_rename_button; 34 | GtkButton *collection_delete_button; 35 | }; 36 | 37 | static void 38 | config_decsync_context_free (Context *context) 39 | { 40 | g_free (context->orig_color); 41 | g_object_unref (context->decsync_dir_button); 42 | g_object_unref (context->collection_combo_box); 43 | g_object_unref (context->collection_rename_button); 44 | g_object_unref (context->collection_delete_button); 45 | 46 | g_slice_free (Context, context); 47 | } 48 | 49 | static gchar * 50 | getInfo (const gchar *decsyncDir, const gchar *syncType, const gchar *collection, const gchar *name, const gchar *fallback) 51 | { 52 | bool deleted; 53 | JsonNode *key_node, *value_node; 54 | gchar *key_string, value_string[256], *result; 55 | GError *error = NULL; 56 | 57 | key_node = json_node_new (JSON_NODE_VALUE); 58 | json_node_set_string (key_node, "deleted"); 59 | key_string = json_to_string (key_node, FALSE); 60 | decsync_get_static_info (decsyncDir, syncType, collection, key_string, value_string, 256); 61 | json_node_free (key_node); 62 | g_free (key_string); 63 | value_node = json_from_string (value_string, &error); 64 | if (error != NULL) { 65 | g_warning ("Invalid JSON for static info 'deleted': %s", value_string); 66 | g_error_free (error); 67 | return NULL; 68 | } 69 | deleted = !json_node_is_null (value_node) && json_node_get_boolean (value_node); 70 | json_node_free (value_node); 71 | if (deleted) 72 | return NULL; 73 | 74 | key_node = json_node_new (JSON_NODE_VALUE); 75 | json_node_set_string (key_node, name); 76 | key_string = json_to_string (key_node, FALSE); 77 | decsync_get_static_info (decsyncDir, syncType, collection, key_string, value_string, 256); 78 | json_node_free (key_node); 79 | g_free (key_string); 80 | value_node = json_from_string (value_string, &error); 81 | if (error != NULL) { 82 | g_warning ("Invalid JSON for static info '%s': %s", name, value_string); 83 | g_error_free (error); 84 | return NULL; 85 | } 86 | result = g_strdup (json_node_is_null (value_node) ? fallback : json_node_get_string (value_node)); 87 | json_node_free (value_node); 88 | return result; 89 | } 90 | 91 | static void 92 | setInfoEntry (const gchar *decsyncDir, const gchar *syncType, const gchar *collection, const gchar *name, JsonNode *value_node) 93 | { 94 | Decsync decsync; 95 | const gchar *path[1]; 96 | gchar ownAppId[256]; 97 | JsonNode *key_node; 98 | gchar *key_string, *value_string; 99 | 100 | decsync_get_app_id ("Evolution", ownAppId, 256); 101 | decsync_new (&decsync, decsyncDir, syncType, collection, ownAppId); 102 | path[0] = "info"; 103 | key_node = json_node_new (JSON_NODE_VALUE); 104 | json_node_set_string (key_node, name); 105 | key_string = json_to_string (key_node, FALSE); 106 | value_string = json_to_string (value_node, FALSE); 107 | decsync_set_entry (decsync, path, 1, key_string, value_string); 108 | json_node_free (key_node); 109 | g_free (key_string); 110 | g_free (value_string); 111 | decsync_free (decsync); 112 | } 113 | 114 | static gchar * 115 | createCollection (const gchar *decsyncDir, const gchar *syncType, const gchar *name) 116 | { 117 | gchar *collection; 118 | JsonNode *value_node; 119 | 120 | collection = g_strdup_printf ("colID%05d", rand () % 100000); 121 | value_node = json_node_new (JSON_NODE_VALUE); 122 | json_node_set_string (value_node, name); 123 | setInfoEntry(decsyncDir, syncType, collection, "name", value_node); 124 | json_node_free (value_node); 125 | return collection; 126 | } 127 | 128 | static void 129 | config_decsync_update_color (Context *context) 130 | { 131 | ESourceExtension *extension; 132 | const gchar *extension_name, *decsync_dir, *collection; 133 | gchar *color; 134 | 135 | if (g_strcmp0(context->sync_type, "contacts") == 0) return; 136 | 137 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 138 | extension = e_source_get_extension (context->scratch_source, extension_name); 139 | decsync_dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 140 | collection = e_source_decsync_get_collection (E_SOURCE_DECSYNC (extension)); 141 | 142 | if (decsync_dir != NULL && *decsync_dir != '\0' && collection != NULL && *collection != '\0') 143 | color = getInfo (decsync_dir, context->sync_type, collection, "color", context->orig_color); 144 | else 145 | color = g_strdup (context->orig_color); 146 | 147 | if (g_strcmp0 (context->sync_type, "calendars") == 0) 148 | extension_name = E_SOURCE_EXTENSION_CALENDAR; 149 | else if (g_strcmp0 (context->sync_type, "tasks") == 0) 150 | extension_name = E_SOURCE_EXTENSION_TASK_LIST; 151 | else if (g_strcmp0 (context->sync_type, "memos") == 0) 152 | extension_name = E_SOURCE_EXTENSION_MEMO_LIST; 153 | else 154 | return; 155 | extension = e_source_get_extension (context->scratch_source, extension_name); 156 | e_source_selectable_set_color (E_SOURCE_SELECTABLE (extension), color); 157 | g_free (color); 158 | } 159 | 160 | static void 161 | config_decsync_update_combo_box (Context *context) 162 | { 163 | ESourceConfig *config; 164 | ESourceExtension *extension; 165 | const gchar *extension_name, *decsync_dir, *collection, *title, *error_string; 166 | gchar collections[256][256], *name; 167 | int ii, length, error; 168 | GtkWidget *dialog; 169 | gpointer parent; 170 | 171 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 172 | extension = e_source_get_extension (context->scratch_source, extension_name); 173 | decsync_dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 174 | 175 | gtk_combo_box_text_remove_all (context->collection_combo_box); 176 | 177 | error = decsync_check_decsync_info (decsync_dir); 178 | if (error != 0) { 179 | switch (error) { 180 | case 1: 181 | error_string = "Invalid .decsync-info"; 182 | break; 183 | case 2: 184 | error_string = "Unsupported DecSync version"; 185 | break; 186 | default: 187 | error_string = "Unknown error"; 188 | break; 189 | } 190 | config = e_source_config_backend_get_config (context->backend); 191 | parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); 192 | parent = gtk_widget_is_toplevel (parent) ? parent : NULL; 193 | dialog = gtk_message_dialog_new (parent, 194 | GTK_DIALOG_DESTROY_WITH_PARENT, 195 | GTK_MESSAGE_WARNING, 196 | GTK_BUTTONS_OK, 197 | "%s", 198 | error_string); 199 | title = _("DecSync"); 200 | gtk_window_set_title (GTK_WINDOW (dialog), title); 201 | 202 | gtk_dialog_run (GTK_DIALOG (dialog)); 203 | gtk_widget_destroy (dialog); 204 | } else if (decsync_dir != NULL && *decsync_dir != '\0') { 205 | length = decsync_list_collections (decsync_dir, context->sync_type, collections, 256); 206 | for (ii = 0; ii < length; ii++) { 207 | collection = collections[ii]; 208 | name = getInfo (decsync_dir, context->sync_type, collection, "name", collection); 209 | if (name != NULL && *name != '\0') 210 | gtk_combo_box_text_append (context->collection_combo_box, collection, name); 211 | g_free (name); 212 | } 213 | gtk_combo_box_text_append (context->collection_combo_box, "", _("New...")); 214 | } 215 | 216 | collection = e_source_decsync_get_collection (E_SOURCE_DECSYNC (extension)); 217 | if (collection != NULL && *collection != '\0') 218 | gtk_combo_box_set_active_id (GTK_COMBO_BOX (context->collection_combo_box), collection); 219 | 220 | config_decsync_update_color (context); 221 | } 222 | 223 | static void 224 | config_decsync_dir_cb (GtkButton *button, Context *context) 225 | { 226 | ESourceConfig *config; 227 | ESourceExtension *extension; 228 | const gchar *extension_name, *decsync_dir; 229 | GtkWidget *dialog; 230 | gpointer parent; 231 | 232 | config = e_source_config_backend_get_config (context->backend); 233 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 234 | extension = e_source_get_extension (context->scratch_source, extension_name); 235 | 236 | decsync_dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 237 | 238 | parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); 239 | parent = gtk_widget_is_toplevel (parent) ? parent : NULL; 240 | 241 | dialog = gtk_file_chooser_dialog_new ( 242 | _("Select DecSync directory"), parent, 243 | GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, 244 | _("_Cancel"), GTK_RESPONSE_CANCEL, 245 | _("_OK"), GTK_RESPONSE_ACCEPT, 246 | NULL); 247 | gtk_file_chooser_set_create_folders (GTK_FILE_CHOOSER (dialog), TRUE); 248 | gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (dialog), TRUE); 249 | gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), decsync_dir); 250 | 251 | if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { 252 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 253 | extension = e_source_get_extension (context->scratch_source, extension_name); 254 | decsync_dir = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); 255 | gtk_button_set_label (context->decsync_dir_button, decsync_dir); 256 | e_source_decsync_set_decsync_dir (E_SOURCE_DECSYNC (extension), decsync_dir); 257 | e_source_decsync_set_collection (E_SOURCE_DECSYNC (extension), NULL); 258 | config_decsync_update_combo_box (context); 259 | } 260 | gtk_widget_destroy (dialog); 261 | } 262 | 263 | static void 264 | config_decsync_collection_set_cb (GtkComboBox *combo_box, Context *context) 265 | { 266 | ESourceConfig *config; 267 | ESourceExtension *extension; 268 | const gchar *extension_name, *id, *dir, *name; 269 | gchar *collection, *title; 270 | GtkWidget *dialog, *container, *widget; 271 | gpointer parent; 272 | 273 | config = e_source_config_backend_get_config (context->backend); 274 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 275 | extension = e_source_get_extension (context->scratch_source, extension_name); 276 | 277 | id = gtk_combo_box_get_active_id (combo_box); 278 | if (id == NULL) { 279 | gtk_widget_set_sensitive (GTK_WIDGET (context->collection_rename_button), FALSE); 280 | gtk_widget_set_sensitive (GTK_WIDGET (context->collection_delete_button), FALSE); 281 | return; 282 | } else { 283 | gtk_widget_set_sensitive (GTK_WIDGET (context->collection_rename_button), TRUE); 284 | gtk_widget_set_sensitive (GTK_WIDGET (context->collection_delete_button), TRUE); 285 | } 286 | 287 | if (*id == '\0') { 288 | parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); 289 | parent = gtk_widget_is_toplevel (parent) ? parent : NULL; 290 | title = g_strdup_printf (_("Name for new %s"), context->sync_type_title); 291 | dialog = gtk_dialog_new_with_buttons (title, parent, GTK_DIALOG_DESTROY_WITH_PARENT, 292 | _("_Cancel"), GTK_RESPONSE_REJECT, 293 | _("_OK"), GTK_RESPONSE_ACCEPT, 294 | NULL); 295 | g_free (title); 296 | 297 | widget = gtk_entry_new (); 298 | 299 | container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); 300 | gtk_container_add (GTK_CONTAINER (container), widget); 301 | gtk_widget_show_all (dialog); 302 | 303 | if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { 304 | name = gtk_entry_get_text (GTK_ENTRY (widget)); 305 | if (name != NULL && *name != '\0') { 306 | dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 307 | collection = createCollection (dir, context->sync_type, name); 308 | e_source_decsync_set_collection (E_SOURCE_DECSYNC (extension), collection); 309 | g_free (collection); 310 | } 311 | } 312 | gtk_widget_destroy (dialog); 313 | config_decsync_update_combo_box (context); 314 | } else { 315 | e_source_decsync_set_collection (E_SOURCE_DECSYNC (extension), id); 316 | name = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box)); 317 | e_source_set_display_name (context->scratch_source, name); 318 | config_decsync_update_color (context); 319 | } 320 | } 321 | 322 | static void 323 | config_decsync_collection_rename_cb (GtkButton *button, Context *context) 324 | { 325 | ESourceConfig *config; 326 | ESourceExtension *extension; 327 | const gchar *extension_name, *dir, *collection, *name_old, *name; 328 | gchar *title; 329 | GtkWidget *dialog, *container, *widget; 330 | gpointer parent; 331 | gint position; 332 | JsonNode *value_node; 333 | 334 | config = e_source_config_backend_get_config (context->backend); 335 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 336 | extension = e_source_get_extension (context->scratch_source, extension_name); 337 | 338 | dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 339 | collection = e_source_decsync_get_collection (E_SOURCE_DECSYNC (extension)); 340 | 341 | parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); 342 | parent = gtk_widget_is_toplevel (parent) ? parent : NULL; 343 | 344 | title = g_strdup_printf (_("New name for %s"), context->sync_type_title); 345 | dialog = gtk_dialog_new_with_buttons (title, parent, GTK_DIALOG_DESTROY_WITH_PARENT, 346 | _("_Cancel"), GTK_RESPONSE_REJECT, 347 | _("_OK"), GTK_RESPONSE_ACCEPT, 348 | NULL); 349 | g_free (title); 350 | 351 | widget = gtk_entry_new (); 352 | name_old = gtk_combo_box_text_get_active_text (context->collection_combo_box); 353 | position = gtk_combo_box_get_active (GTK_COMBO_BOX (context->collection_combo_box)); 354 | gtk_entry_set_text (GTK_ENTRY (widget), name_old); 355 | 356 | container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); 357 | gtk_container_add (GTK_CONTAINER (container), widget); 358 | gtk_widget_show_all (dialog); 359 | 360 | if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { 361 | name = gtk_entry_get_text (GTK_ENTRY (widget)); 362 | if (name != NULL && *name != '\0' && g_strcmp0 (name, name_old)) { 363 | dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 364 | value_node = json_node_new (JSON_NODE_VALUE); 365 | json_node_set_string (value_node, name); 366 | setInfoEntry (dir, context->sync_type, collection, "name", value_node); 367 | json_node_free (value_node); 368 | gtk_combo_box_text_remove (context->collection_combo_box, position); 369 | gtk_combo_box_text_insert (context->collection_combo_box, position, collection, name); 370 | gtk_combo_box_set_active_id (GTK_COMBO_BOX (context->collection_combo_box), collection); 371 | } 372 | } 373 | gtk_widget_destroy (dialog); 374 | } 375 | 376 | static void 377 | config_decsync_collection_delete_cb (GtkButton *button, Context *context) 378 | { 379 | ESourceConfig *config; 380 | ESourceExtension *extension; 381 | const gchar *extension_name, *dir, *collection, *name; 382 | gchar *title; 383 | GtkWidget *dialog; 384 | gpointer parent; 385 | gint position; 386 | JsonNode *value_node; 387 | 388 | config = e_source_config_backend_get_config (context->backend); 389 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 390 | extension = e_source_get_extension (context->scratch_source, extension_name); 391 | 392 | dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 393 | collection = e_source_decsync_get_collection (E_SOURCE_DECSYNC (extension)); 394 | name = gtk_combo_box_text_get_active_text (context->collection_combo_box); 395 | 396 | parent = gtk_widget_get_toplevel (GTK_WIDGET (config)); 397 | parent = gtk_widget_is_toplevel (parent) ? parent : NULL; 398 | 399 | dialog = gtk_message_dialog_new (parent, 400 | GTK_DIALOG_DESTROY_WITH_PARENT, 401 | GTK_MESSAGE_QUESTION, 402 | GTK_BUTTONS_YES_NO, 403 | _("Are you sure you want to delete the %s '%s'?"), 404 | context->sync_type_title, 405 | name); 406 | title = g_strdup_printf (_("Delete %s"), context->sync_type_title); 407 | gtk_window_set_title (GTK_WINDOW (dialog), title); 408 | g_free (title); 409 | 410 | if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES) { 411 | value_node = json_node_new (JSON_NODE_VALUE); 412 | json_node_set_boolean (value_node, TRUE); 413 | setInfoEntry (dir, context->sync_type, collection, "deleted", value_node); 414 | json_node_free (value_node); 415 | position = gtk_combo_box_get_active (GTK_COMBO_BOX (context->collection_combo_box)); 416 | gtk_combo_box_text_remove (context->collection_combo_box, position); 417 | } 418 | gtk_widget_destroy (dialog); 419 | } 420 | 421 | void 422 | config_decsync_insert_widgets (const gchar *sync_type, const gchar *sync_type_title, ESourceConfigBackend *backend, ESource *scratch_source) 423 | { 424 | ESourceConfig *config; 425 | ESourceExtension *extension; 426 | GtkWidget *widget, *container; 427 | Context *context; 428 | const gchar *extension_name, *uid, *decsync_dir; 429 | gchar *title, default_decsync_dir[256]; 430 | gboolean is_new_collection; 431 | 432 | uid = e_source_get_uid (scratch_source); 433 | config = e_source_config_backend_get_config (backend); 434 | context = g_slice_new (Context); 435 | 436 | context->backend = backend; 437 | context->scratch_source = scratch_source; 438 | context->sync_type = sync_type; 439 | context->sync_type_title = sync_type_title; 440 | 441 | if (g_strcmp0 (sync_type, "calendars") == 0) 442 | extension_name = E_SOURCE_EXTENSION_CALENDAR; 443 | else if (g_strcmp0 (sync_type, "tasks") == 0) 444 | extension_name = E_SOURCE_EXTENSION_TASK_LIST; 445 | else if (g_strcmp0 (sync_type, "memos") == 0) 446 | extension_name = E_SOURCE_EXTENSION_MEMO_LIST; 447 | else 448 | extension_name = NULL; 449 | if (extension_name != NULL) { 450 | extension = e_source_get_extension (scratch_source, extension_name); 451 | context->orig_color = e_source_selectable_dup_color (E_SOURCE_SELECTABLE (extension)); 452 | } else { 453 | context->orig_color = NULL; 454 | } 455 | 456 | g_object_set_data_full ( 457 | G_OBJECT (backend), uid, context, 458 | (GDestroyNotify) config_decsync_context_free); 459 | 460 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 461 | extension = e_source_get_extension (scratch_source, extension_name); 462 | decsync_dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 463 | if (decsync_dir == NULL || *decsync_dir == '\0') { 464 | is_new_collection = TRUE; 465 | decsync_get_default_dir (default_decsync_dir, 256); 466 | decsync_dir = default_decsync_dir; 467 | e_source_decsync_set_decsync_dir (E_SOURCE_DECSYNC (extension), decsync_dir); 468 | } else { 469 | is_new_collection = FALSE; 470 | } 471 | 472 | widget = gtk_button_new_with_label (decsync_dir); 473 | gtk_widget_set_sensitive (widget, is_new_collection); 474 | 475 | e_source_config_insert_widget ( 476 | config, scratch_source, _("Directory:"), widget); 477 | context->decsync_dir_button = GTK_BUTTON (g_object_ref (widget)); 478 | gtk_widget_show (widget); 479 | 480 | g_signal_connect ( 481 | widget, "clicked", 482 | G_CALLBACK (config_decsync_dir_cb), 483 | context); 484 | 485 | container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); 486 | 487 | widget = gtk_combo_box_text_new (); 488 | gtk_widget_set_sensitive (widget, is_new_collection); 489 | gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); 490 | context->collection_combo_box = GTK_COMBO_BOX_TEXT (g_object_ref (widget)); 491 | gtk_widget_show (widget); 492 | 493 | g_signal_connect ( 494 | widget, "changed", 495 | G_CALLBACK (config_decsync_collection_set_cb), 496 | context); 497 | 498 | widget = gtk_button_new_with_label (_("Rename")); 499 | gtk_widget_set_sensitive (widget, FALSE); 500 | gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); 501 | context->collection_rename_button = GTK_BUTTON (g_object_ref (widget)); 502 | gtk_widget_show (widget); 503 | 504 | g_signal_connect ( 505 | widget, "clicked", 506 | G_CALLBACK (config_decsync_collection_rename_cb), 507 | context); 508 | 509 | widget = gtk_button_new_with_label (_("Delete")); 510 | gtk_widget_set_sensitive (widget, FALSE); 511 | gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); 512 | context->collection_delete_button = GTK_BUTTON (g_object_ref (widget)); 513 | gtk_widget_show (widget); 514 | 515 | g_signal_connect ( 516 | widget, "clicked", 517 | G_CALLBACK (config_decsync_collection_delete_cb), 518 | context); 519 | 520 | title = g_strdup_printf (_("%s:"), sync_type_title); 521 | e_source_config_insert_widget ( 522 | config, scratch_source, title, GTK_WIDGET (container)); 523 | gtk_widget_show (GTK_WIDGET (container)); 524 | g_free (title); 525 | 526 | config_decsync_update_combo_box (context); 527 | 528 | e_source_config_add_refresh_interval (config, scratch_source); 529 | } 530 | 531 | gboolean 532 | config_decsync_check_complete (ESourceConfigBackend *backend, ESource *scratch_source) 533 | { 534 | ESourceDecsync *extension; 535 | Context *context; 536 | const gchar *uid, *extension_name, *dir, *collection; 537 | 538 | uid = e_source_get_uid (scratch_source); 539 | context = g_object_get_data (G_OBJECT (backend), uid); 540 | 541 | /* This function might get called before we install a 542 | * context for this ESource, in which case just return. */ 543 | if (context == NULL) 544 | return FALSE; 545 | 546 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 547 | extension = e_source_get_extension (scratch_source, extension_name); 548 | 549 | dir = e_source_decsync_get_decsync_dir (extension); 550 | if (dir == NULL) 551 | return FALSE; 552 | 553 | collection = e_source_decsync_get_collection (extension); 554 | if (collection == NULL) 555 | return FALSE; 556 | 557 | return *dir != '\0' && *collection != '\0'; 558 | } 559 | 560 | void 561 | config_decsync_commit_changes (ESourceConfigBackend *backend, ESource *scratch_source) 562 | { 563 | ESourceExtension *extension; 564 | Context *context; 565 | const gchar *uid, *extension_name, *decsync_dir, *collection, *old_appid, *new_color; 566 | gchar new_appid[256], *old_color; 567 | JsonNode *value_node; 568 | 569 | uid = e_source_get_uid (scratch_source); 570 | context = g_object_get_data (G_OBJECT (backend), uid); 571 | 572 | extension_name = E_SOURCE_EXTENSION_DECSYNC_BACKEND; 573 | extension = e_source_get_extension (scratch_source, extension_name); 574 | decsync_dir = e_source_decsync_get_decsync_dir (E_SOURCE_DECSYNC (extension)); 575 | collection = e_source_decsync_get_collection (E_SOURCE_DECSYNC (extension)); 576 | 577 | old_appid = e_source_decsync_get_appid (E_SOURCE_DECSYNC (extension)); 578 | if (old_appid == NULL || *old_appid == '\0') { 579 | decsync_get_app_id_with_id ("Evolution", rand () % 100000, new_appid, 256); 580 | e_source_decsync_set_appid (E_SOURCE_DECSYNC (extension), new_appid); 581 | } 582 | 583 | if (g_strcmp0 (context->sync_type, "calendars") == 0) 584 | extension_name = E_SOURCE_EXTENSION_CALENDAR; 585 | else if (g_strcmp0 (context->sync_type, "tasks") == 0) 586 | extension_name = E_SOURCE_EXTENSION_TASK_LIST; 587 | else if (g_strcmp0 (context->sync_type, "memos") == 0) 588 | extension_name = E_SOURCE_EXTENSION_MEMO_LIST; 589 | else 590 | extension_name = NULL; 591 | if (extension_name != NULL) { 592 | extension = e_source_get_extension (scratch_source, extension_name); 593 | new_color = e_source_selectable_get_color (E_SOURCE_SELECTABLE (extension)); 594 | old_color = getInfo (decsync_dir, context->sync_type, collection, "color", NULL); 595 | 596 | if (g_strcmp0 (new_color, old_color)) { 597 | value_node = json_node_new (JSON_NODE_VALUE); 598 | json_node_set_string (value_node, new_color); 599 | setInfoEntry (decsync_dir, context->sync_type, collection, "color", value_node); 600 | json_node_free (value_node); 601 | } 602 | 603 | g_free (old_color); 604 | } 605 | } 606 | 607 | void 608 | config_decsync_add_source_file () 609 | { 610 | const gchar *to_dir; 611 | gchar *from_file_str, *to_file_str; 612 | GFile *from_file, *to_file; 613 | 614 | from_file_str = g_build_filename (E_SOURCE_DIR, "decsync.source", NULL); 615 | from_file = g_file_new_for_path (from_file_str); 616 | g_free (from_file_str); 617 | 618 | to_dir = e_server_side_source_get_user_dir (); 619 | to_file_str = g_build_filename (to_dir, "decsync.source", NULL); 620 | to_file = g_file_new_for_path (to_file_str); 621 | g_free (to_file_str); 622 | 623 | g_file_copy (from_file, to_file, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL); 624 | 625 | g_object_unref (from_file); 626 | g_object_unref (to_file); 627 | } 628 | -------------------------------------------------------------------------------- /src/modules/utils/decsync.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Evolution-DecSync - decsync.h 3 | * 4 | * Copyright (C) 2018 Aldo Gunsing 5 | * 6 | * This library is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 | * for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this library; if not, see . 17 | */ 18 | 19 | #include "evolution-decsync-config.h" 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | 28 | void config_decsync_insert_widgets (const gchar *decsync_type_dirname, const gchar *decsync_type_title, ESourceConfigBackend *backend, ESource *scratch_source); 29 | gboolean config_decsync_check_complete (ESourceConfigBackend *backend, ESource *scratch_source); 30 | void config_decsync_commit_changes (ESourceConfigBackend *backend, ESource *scratch_source); 31 | void config_decsync_add_source_file (); 32 | --------------------------------------------------------------------------------