diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /kpilot/lib | |
download | tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kpilot/lib')
55 files changed, 15100 insertions, 0 deletions
diff --git a/kpilot/lib/CMakeLists.txt b/kpilot/lib/CMakeLists.txt new file mode 100644 index 000000000..efc36ce59 --- /dev/null +++ b/kpilot/lib/CMakeLists.txt @@ -0,0 +1,90 @@ +include(CheckIncludeFiles) +include(CheckFunctionExists) + +check_include_files( stdint.h HAVE_STDINT_H ) +check_include_files( alloca.h HAVE_ALLOCA_H ) +check_include_files( "sys/time.h" HAVE_SYS_TIME_H ) +check_include_files( "sys/stat.h" HAVE_SYS_STAT_H ) +check_function_exists( cfsetspeed HAVE_CFSETSPEED ) +check_function_exists( strdup HAVE_STRDUP ) +check_function_exists( setenv HAVE_SETENV ) +check_function_exists( unsetenv HAVE_UNSETENV ) +check_function_exists( usleep HAVE_USLEEP ) +check_function_exists( random HAVE_RANDOM ) +check_function_exists( putenv HAVE_PUTENV ) +check_function_exists( seteuid HAVE_SETEUID ) +check_function_exists( mkstemps HAVE_MKSTEMPS ) +check_function_exists( mkstemp HAVE_MKSTEMP ) +check_function_exists( mkdtemp HAVE_MKDTEMP ) +check_function_exists( revoke HAVE_REVOKE ) +check_function_exists( strlcpy HAVE_STRLCPY ) +check_function_exists( strlcat HAVE_STRLCAT ) +check_function_exists( inet_aton HAVE_INET_ATON ) + +configure_file( + ${CMAKE_SOURCE_DIR}/config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/config.h +) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(lib_SRCS + options.cc + plugin.cc + syncAction.cc + actions.cc + actionQueue.cc + idmapping.cc + idmapperxml.cc + idmapper.cc + kpilotlink.cc + kpilotdevicelink.cc + kpilotlocallink.cc + pilot.cc + pilotAppInfo.cc + pilotRecord.cc + pilotDatabase.cc + pilotLocalDatabase.cc + pilotSerialDatabase.cc + pilotMemo.cc + pilotAddress.cc + pilotDateEntry.cc + pilotTodoEntry.cc +) + +kde3_automoc(${lib_SRCS}) +kde3_add_kcfg_files(lib_SRCS kpilotlibSettings.kcfgc) +add_library(kpilot SHARED ${lib_SRCS}) +target_link_libraries(kpilot ${PILOTLINK_LIBRARY} ${QT_LIBRARIES} kdeui kio) +kpilot_rpath(kpilot) + +#---------- INSTALL -----------------------* +set(kpilotinclude_HEADERS + kpilotlink.h + kpilotdevicelink.h + kpilotlocallink.h + pilot.h + pilotDatabase.h + pilotLinkVersion.h + pilotLocalDatabase.h + pilotRecord.h + pilotSerialDatabase.h + plugin.h + pluginfactory.h + syncAction.h +) + +install( + TARGETS kpilot + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) + +install( + FILES ${kpilotinclude_HEADERS} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/kpilot +) + +install( + FILES kpilotlib.kcfg DESTINATION ${KDE3_KCFG_DIR} +) + diff --git a/kpilot/lib/COPYING b/kpilot/lib/COPYING new file mode 100644 index 000000000..7bbb8e8bd --- /dev/null +++ b/kpilot/lib/COPYING @@ -0,0 +1,509 @@ +[This license applies only to the contents of the lib/ directory, +whereas the rest of KPilot is under the regular GNU GENERAL PUBLIC +LICENSE. This has been done to ensure that non-GPL yet LGPL-compatibly +licensed plugins may be written for KPilot.] + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kpilot/lib/Makefile.am b/kpilot/lib/Makefile.am new file mode 100644 index 000000000..b2ceadf1f --- /dev/null +++ b/kpilot/lib/Makefile.am @@ -0,0 +1,60 @@ +### Makefile.am for kpilot/lib +### +### + +METASOURCES = AUTO + +INCLUDES = $(PISOCK_INCLUDE) -I$(top_srcdir) $(all_includes) + +### If you must get debugging output on a platform where +### the libs are built without debugging support, define +### DEBUG_CERR. Define DEBUG to get debugging support anywhere. +### +### KDE_CXXFLAGS=-DDEBUG -DDEBUG_CERR +##KDE_CXXFLAGS=-DNDEBUG -UDEBUG +##KDE_CXXFLAGS=-DDEBUG + +lib_LTLIBRARIES = libkpilot.la + +libkpilot_la_SOURCES = kpilotlibSettings.kcfgc \ + options.cc plugin.cc syncAction.cc \ + kpilotlink.cc kpilotdevicelink.cc kpilotlocallink.cc \ + actions.cc actionQueue.cc \ + pilot.cc \ + pilotAppInfo.cc pilotRecord.cc pilotDatabase.cc \ + pilotLocalDatabase.cc pilotSerialDatabase.cc \ + pilotMemo.cc \ + pilotAddress.cc \ + pilotDateEntry.cc \ + pilotTodoEntry.cc + +libkpilot_la_LDFLAGS = $(PISOCK_LDFLAGS) -no-undefined $(all_libraries) $(KDE_EXTRA_RPATH) $(KDE_RPATH) +libkpilot_la_LIBADD = $(PISOCK_LIB) $(LIB_KDEUI) $(LIB_KABC) $(top_builddir)/libkcal/libkcal.la + +kpilotincludedir = $(includedir)/kpilot +kpilotinclude_HEADERS = \ + kpilotlink.h kpilotlocallink.h kpilotdevicelink.h \ + pilot.h \ + pilotDatabase.h \ + pilotLinkVersion.h \ + pilotLocalDatabase.h \ + pilotRecord.h \ + pilotSerialDatabase.h \ + plugin.h \ + pluginfactory.h \ + syncAction.h + + +kde_kcfg_DATA = kpilotlib.kcfg + +check-local: + rm -f FAILED + for i in $(srcdir)/*.h ; do \ + ( echo "#include <kdemacros.h>" ; echo "#include \"$$i\"" ; echo "int main(int argc,char **argv){return 0;}" ) > header-test.cc; \ + echo "$$i" ; \ + g++ $(all_includes) -I$(top_builddir) -DQT_THREAD_SUPPORT -c header-test.cc || echo "$$i" >> FAILED; \ + done + test ! -e FAILED + +DOXYGEN_REFERENCES=libkcal kdecore +include $(top_srcdir)/admin/Doxyfile.am diff --git a/kpilot/lib/actionQueue.cc b/kpilot/lib/actionQueue.cc new file mode 100644 index 000000000..e4770d369 --- /dev/null +++ b/kpilot/lib/actionQueue.cc @@ -0,0 +1,172 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This defines the "ActionQueue", which is the pile of actions +** that will occur during a HotSync. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ +#include "options.h" + +#include <qtimer.h> + +#include "actions.h" +#include "plugin.h" + +#include "actionQueue.moc" + + + + +ActionQueue::ActionQueue(KPilotLink *d) : + SyncAction(d,"ActionQueue") + // The string lists have default constructors +{ + FUNCTIONSETUP; +} + +ActionQueue::~ActionQueue() +{ + FUNCTIONSETUP; + clear(); +} + +void ActionQueue::clear() +{ + SyncAction *del = 0L; + while ( (del = nextAction()) ) + { + delete del; + } + + Q_ASSERT(isEmpty()); +} + +void ActionQueue::queueInit() +{ + FUNCTIONSETUP; + + addAction(new WelcomeAction(fHandle)); +} + +void ActionQueue::queueConduits(const QStringList &l, + const SyncAction::SyncMode &m) +{ + FUNCTIONSETUP; + + // Add conduits here ... + // + // + for (QStringList::ConstIterator it = l.begin(); + it != l.end(); + ++it) + { + if ((*it).startsWith(CSL1("internal_"))) + { +#ifdef DEBUG + DEBUGKPILOT << fname << + ": Ignoring conduit " << *it << endl; +#endif + continue; + } + +#ifdef DEBUG + DEBUGKPILOT << fname + << ": Creating proxy with mode=" << m.name() << endl; +#endif + ConduitProxy *cp = new ConduitProxy(fHandle,*it,m); + addAction(cp); + } +} + +void ActionQueue::queueCleanup() +{ + addAction(new CleanupAction(fHandle)); +} + +bool ActionQueue::exec() +{ + actionCompleted(0L); + return true; +} + +void ActionQueue::actionCompleted(SyncAction *b) +{ + FUNCTIONSETUP; + + if (b) + { +#ifdef DEBUG + DEBUGKPILOT << fname + << ": Completed action " + << b->name() + << endl; +#endif + delete b; + } + + if (isEmpty()) + { + delayDone(); + return; + } + if ( deviceLink() && (!deviceLink()->tickle()) ) + { + emit logError(i18n("The connection to the handheld " + "was lost. Synchronization cannot continue.")); + clear(); + delayDone(); + return; + } + + SyncAction *a = nextAction(); + + if (!a) + { + WARNINGKPILOT << "NULL action on stack." + << endl; + return; + } + +#ifdef DEBUG + DEBUGKPILOT << fname + << ": Will run action " + << a->name() + << endl; +#endif + + QObject::connect(a, SIGNAL(logMessage(const QString &)), + this, SIGNAL(logMessage(const QString &))); + QObject::connect(a, SIGNAL(logError(const QString &)), + this, SIGNAL(logMessage(const QString &))); + QObject::connect(a, SIGNAL(logProgress(const QString &, int)), + this, SIGNAL(logProgress(const QString &, int))); + QObject::connect(a, SIGNAL(syncDone(SyncAction *)), + this, SLOT(actionCompleted(SyncAction *))); + + // Run the action picked from the queue when we get back + // to the event loop. + QTimer::singleShot(0,a,SLOT(execConduit())); +} + diff --git a/kpilot/lib/actionQueue.h b/kpilot/lib/actionQueue.h new file mode 100644 index 000000000..bc8b11560 --- /dev/null +++ b/kpilot/lib/actionQueue.h @@ -0,0 +1,162 @@ +#ifndef _KPILOT_ACTIONQUEUE_H +#define _KPILOT_ACTIONQUEUE_H +/* +** +** Copyright (C) 1998-2001,2003 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qptrqueue.h> + +#include "syncAction.h" + +/** @file +* This file defines the ActionQueue. +* +* This used to be called SyncStack, and while a stack is cool +* for some things, it actually rather confuses the issue because +* you _use_ this class for specifying "do this, then that, then ..." +* and in program code you need to reverse that order when adding +* items to a stack. So now it's a Queue, FIFO, and program +* code looks more normal. +*/ + +/** +* The ActionQueue is a meta-action, which handles running a bunch of +* SyncActions in sequence. It is a SyncAction itself, so it can even +* be queued on another ActionQueue. +* +* An ActionQueue is constructed with a @p device. As usual, you should +* connect the device's deviceReady() signal with the exec() slot -- +* or something to that effect. The ActionQueue will then run all the +* actions in the queue in sequence. +* +*/ +KDE_EXPORT class ActionQueue : public SyncAction +{ +Q_OBJECT +public: + /** + * Constructor. Pass in a KPilot device link for it to act on. + * It is legal to pass in 0 (NULL) as a device. Ownership of + * the device is unchanged. + */ + ActionQueue(KPilotLink *device); + + /** Destructor. */ + virtual ~ActionQueue(); + + /** Is the queue empty? Returns @c true if it is. */ + bool isEmpty() const + { + return SyncActionQueue.isEmpty(); + }; + + /** + * You can push your own action @p a onto the queue. Ownership + * of the action is given to the ActionQueue object. + */ + void addAction(SyncAction *a) + { + SyncActionQueue.enqueue(a); + }; + +public: + /* + * Call these queue*() functions to append standard functional + * blocks. You should at least call queueInit() and + * queueCleanup() to add the welcome and cleanup actions to + * the queue (unless you do that yourself.) + * + * For queueInit, a WelcomeAction is added. + * For queueConduits, whatever is relevant for the conduits + * can be used, usually things in the FlagMask and ActionMask. + * The list of conduits in @p conduits is queued automatically. + */ + + /** + * Initialize the queue. This empties it out and adds a + * welcome action (see WelcomeAction in actions.h) so that + * the user knows what is happening when the ActionQueue + * begins to execute. Equivalent to + * @code + * clear(); addAction(new WelcomeAction); + * @endcode + */ + void queueInit(); + + /** + * Queue a (series) of conduits @p conduits with a given + * sync mode @p mode. Each of the conduits named is called + * through a ConduitProxy object which handles loading the + * conduit's shared library and creating the actual SyncAction + * for that conduit. Actions named "internal_*" are silently + * ignored since those names are used by KPilot internally + * for administrative purposes. + */ + void queueConduits(const QStringList &conduits, + const SyncAction::SyncMode &mode); + + /** + * Convenience function for adding a cleanup action (see + * CleanupAction in actions.h) to the queue. Should be the + * last action added to the queue because a HotSync can only + * have @em one cleanup. + */ + void queueCleanup(); + +protected: + /** + * Remove all the actions from the queue and delete them + * (the queue owns the actions, after all). + */ + void clear(); + + /** + * Dequeue the next action in the queue, ready for processing. + * This takes the action off the queue, so remember to delete it + * eventually. + */ + SyncAction *nextAction() + { + return SyncActionQueue.dequeue(); + }; + + /** Reimplemented from SyncAction. */ + virtual bool exec(); + +protected slots: + /** + * When one action finishes, start the next one. + */ + void actionCompleted(SyncAction *); + +private: + /** A queue of actions to take. */ + QPtrQueue < SyncAction > SyncActionQueue; +}; + + +#endif diff --git a/kpilot/lib/actions.cc b/kpilot/lib/actions.cc new file mode 100644 index 000000000..6cd88273b --- /dev/null +++ b/kpilot/lib/actions.cc @@ -0,0 +1,137 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ +#include "options.h" + +#include <qapplication.h> +#include <qdir.h> +#include <qfile.h> + +#include <ksavefile.h> + +#include "pilot.h" +#include "pilotUser.h" + +#include "actions.h" + + + +WelcomeAction::WelcomeAction(KPilotLink *p) : + SyncAction(p,"welcomeAction") +{ + FUNCTIONSETUP; +} + +/* virtual */ bool WelcomeAction::exec() +{ + FUNCTIONSETUP; + + addSyncLogEntry(i18n("KPilot %1 HotSync starting...\n") + .arg(QString::fromLatin1(KPILOT_VERSION))); + emit logMessage( i18n("Using encoding %1 on the handheld.").arg(Pilot::codecName()) ); + emit syncDone(this); + return true; +} + +SorryAction::SorryAction(KPilotLink *p, const QString &s) : + SyncAction(p,"sorryAction"), + fMessage(s) +{ + if (fMessage.isEmpty()) + { + fMessage = i18n("KPilot is busy and cannot process the " + "HotSync right now."); + } +} + +bool SorryAction::exec() +{ + FUNCTIONSETUP; + + addSyncLogEntry(fMessage); + return delayDone(); +} + +CleanupAction::CleanupAction(KPilotLink *p) : SyncAction(p,"cleanupAction") +{ + FUNCTIONSETUP; +} + +/* virtual */ bool CleanupAction::exec() +{ + FUNCTIONSETUP; + + if (deviceLink()) + { + deviceLink()->endSync( KPilotLink::UpdateUserInfo ); + } + emit syncDone(this); + return true; +} + + +TestLink::TestLink(KPilotLink * p) : + SyncAction(p, "testLink") +{ + FUNCTIONSETUP; + +} + +/* virtual */ bool TestLink::exec() +{ + FUNCTIONSETUP; + + int i; + int dbindex = 0; + int count = 0; + struct DBInfo db; + + addSyncLogEntry(i18n("Testing.\n")); + + while ((i = deviceLink()->getNextDatabase(dbindex,&db)) > 0) + { + count++; + dbindex = db.index + 1; + + DEBUGKPILOT << fname + << ": Read database " << db.name + << " with index " << db.index + << endl; + + // Let the Pilot User know what's happening + openConduit(); + // Let the KDE User know what's happening + // Pretty sure all database names are in latin1. + emit logMessage(i18n("Syncing database %1...") + .arg(Pilot::fromPilot(db.name))); + } + + emit logMessage(i18n("HotSync finished.")); + emit syncDone(this); + return true; +} diff --git a/kpilot/lib/actions.h b/kpilot/lib/actions.h new file mode 100644 index 000000000..ee353551f --- /dev/null +++ b/kpilot/lib/actions.h @@ -0,0 +1,115 @@ +#ifndef _KPILOT_ACTIONS_H +#define _KPILOT_ACTIONS_H +/* +** +** Copyright (C) 1998-2001,2003 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "syncAction.h" + +/** @file +* This file defines some simple standard actions. None of these +* actions require much in the way of configuration; none provide +* particularly complicated functionality. +*/ + +/** +* This action puts "Welcome to KPilot" in the sync log of the handheld. +* It is added automatically to a ActionQueue by queueInit() in order +* to inform the user of the sync. +*/ +KDE_EXPORT class WelcomeAction : public SyncAction +{ +public: + /** Constructor. */ + WelcomeAction(KPilotLink *); + +protected: + /** Reimplemented from SyncAction. */ + virtual bool exec(); +} ; + +/** +* This one just says "sorry, can't sync now". This is used +* in cases when the hotsync starts while KPilot is busy configuring +* something and can't be interrupted. +*/ +KDE_EXPORT class SorryAction : public SyncAction +{ +public: + /** + * Constructor. The action will be executed on the given + * link @p device . If the given string @p s is non-empty, + * print that message (it must be i18n()ed already) instead of + * the standard message. + */ + SorryAction(KPilotLink *device, const QString &s=QString::null); + +protected: + /** Reimplemented from SyncAction. */ + virtual bool exec(); + + /** Message to print to the sync log. */ + QString fMessage; +} ; + +/** +* End the HotSync. This action cleans up the handheld and +* removes cruft. There should be exactly @em one CleanupAction +* executed during a HotSync. Since this action informs the +* device that the HotSync is over, it should be the last +* action executed. +*/ +KDE_EXPORT class CleanupAction : public SyncAction +{ +public: + /** Constructor. */ + CleanupAction(KPilotLink *device); + +protected: + /** Reimplemented from SyncAction. */ + virtual bool exec(); +} ; + +/** +* This action is intended to test the link with the handheld +* and not do anything spectacular. It lists all the databases +* on the handheld in the sync log. +*/ +KDE_EXPORT class TestLink : public SyncAction +{ +public: + /** Constructor. */ + TestLink(KPilotLink *device); + +protected: + /** Reimplemented from SyncAction. */ + virtual bool exec(); +} ; + + +#endif diff --git a/kpilot/lib/idmapper.cc b/kpilot/lib/idmapper.cc new file mode 100644 index 000000000..6e1031efe --- /dev/null +++ b/kpilot/lib/idmapper.cc @@ -0,0 +1,247 @@ +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "idmapper.h" +#include "idmapperxml.h" +#include "options.h" + +#include <qsqldatabase.h> +#include <qfile.h> + +#include <kglobal.h> +#include <kstandarddirs.h> + +class IDMapperPrivate +{ +public: + IDMapperPrivate() + { + fXmlSource = 0L; + } + + ~IDMapperPrivate() + { + FUNCTIONSETUP; + + KPILOT_DELETE(fXmlSource); + } + + IDMapperXml *fXmlSource; +}; + +IDMapper::IDMapper() +{ + FUNCTIONSETUP; + + fP = new IDMapperPrivate(); + + QString dbPath = KGlobal::dirs()-> + saveLocation("data", CSL1("kpilot/") ); + QString dbFile = dbPath + CSL1("mapping.xml"); + + if( !openDatasource( dbFile ) ) + { + DEBUGKPILOT << fname << "Could not open or create xml file." << endl; + } +} + +IDMapper::IDMapper( const QString &file) +{ + FUNCTIONSETUP; + + fP = new IDMapperPrivate(); + + if( !openDatasource( file ) ) + { + DEBUGKPILOT << fname << "Could not open or create xml file." << endl; + } +} + +IDMapper::~IDMapper() +{ + KPILOT_DELETE(fP); +} + +bool IDMapper::openDatasource( const QString &file ) +{ + FUNCTIONSETUP; + + fP->fXmlSource = new IDMapperXml( file ); + return fP->fXmlSource->open(); +} + +void IDMapper::registerPCObjectId( const QString &conduit, const QString &uid ) +{ + FUNCTIONSETUP; + + IDMapping mapping = IDMapping( conduit ); + mapping.setUid( uid ); + + fP->fXmlSource->addMapping( mapping ); + fP->fXmlSource->save(); +} + +void IDMapper::registerHHObjectId( const QString &conduit, recordid_t pid ) +{ + FUNCTIONSETUP; + + IDMapping mapping = IDMapping( conduit ); + mapping.setPid( pid ); + + fP->fXmlSource->addMapping( mapping ); + fP->fXmlSource->save(); +} + +QValueList<QString> IDMapper::getPCObjectIds( const QString &conduit ) +{ + FUNCTIONSETUP; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + QValueList<QString> uids; + + DEBUGKPILOT << fname << ": total " << mappings.count() << endl; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = (*it); + + DEBUGKPILOT << fname << ": mapping.conduit() = " << mapping.conduit() << endl; + DEBUGKPILOT << fname << ": conduit = " << conduit << endl; + + if( (mapping.conduit() == conduit) && !mapping.uid().isNull() ) + { + DEBUGKPILOT << fname << ": mapping.conduit() == conduit" << endl; + uids.append( mapping.uid() ); + } + } + + return uids; +} + +QValueList<recordid_t> IDMapper::getHHObjectIds( const QString &conduit ) +{ + FUNCTIONSETUP; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + QValueList<recordid_t> pids; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = *it; + DEBUGKPILOT << fname << ": mapping.conduit() = " << mapping.conduit() << endl; + DEBUGKPILOT << fname << ": " << mapping.pid() << endl; + if( mapping.conduit() == conduit && mapping.pid() != 0 ) + { + DEBUGKPILOT << fname << ": mapping.conduit() == conduit" << endl; + pids.append( mapping.pid() ); + } + } + + return pids; +} + +bool IDMapper::hasPCId( const QString &conduit, recordid_t pid ) +{ + FUNCTIONSETUP; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = *it; + if( mapping.conduit() == conduit && mapping.pid() == pid ) + { + return !mapping.uid().isNull(); + } + } + + return false; +} + +bool IDMapper::hasHHId( const QString &conduit, const QString &uid ) +{ + FUNCTIONSETUP; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = *it; + if( mapping.conduit() == conduit && mapping.uid() == uid ) + { + return mapping.pid() != 0; + } + } + + return false; +} + +void IDMapper::setHHObjectId( const QString &conduit, const QString &uid + , recordid_t pid ) +{ + FUNCTIONSETUP; + + bool modified = false; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = *it; + if( mapping.conduit() == conduit && mapping.uid() == uid ) + { + mapping.setPid( pid ); + fP->fXmlSource->save(); + modified = true; + } + } +} + +void IDMapper::setPCObjectId( const QString &conduit, recordid_t pid + , const QString &uid ) +{ + FUNCTIONSETUP; + + bool modified = false; + + QValueList<IDMapping> &mappings = fP->fXmlSource->mappings(); + QValueList<IDMapping>::iterator it; + + for ( it = mappings.begin(); it != mappings.end(); ++it ) + { + IDMapping &mapping = *it; + if( mapping.conduit() == conduit && mapping.pid() == pid ) + { + mapping.setUid( uid ); + fP->fXmlSource->save(); + modified = true; + } + } +} diff --git a/kpilot/lib/idmapper.h b/kpilot/lib/idmapper.h new file mode 100644 index 000000000..0364dbc93 --- /dev/null +++ b/kpilot/lib/idmapper.h @@ -0,0 +1,159 @@ +#ifndef _KPILOT_IDMAPPER_H +#define _KPILOT_IDMAPPER_H +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qstring.h> +#include <qdatetime.h> +#include <qvaluelist.h> + +#include "pi-macros.h" + +#include <kconfig.h> + +class IDMapperPrivate; + +/** + * Much of the conduits are recordbased. This class can be used bij the conduits + * to keep track of the mapping between records on the handheld and records on + * the pc. + */ +class IDMapper +{ +public: + /** + * Creates a new IDMapper with default datasource. + */ + IDMapper(); + + /** + * Creates a new IDMapper with file as datasource. + */ + IDMapper( const QString &file ); + + ~IDMapper(); + + /** + * Adds an uid for PC objects to the database. + */ + void registerPCObjectId( const QString &conduit, const QString &uid ); + + /** + * Returns all known uid's for given conduit. + */ + QValueList<QString> getPCObjectIds( const QString &conduit ); + + /** + * Adds a pid for HH objects to the database. + */ + void registerHHObjectId( const QString &conduit, recordid_t pid ); + + /** + * Returns all know pids for given conduit. + */ + QValueList<recordid_t> getHHObjectIds( const QString &conduit ); + + /** + * Sets the PC uid for the handheld record with pid. Does nothing when + * there is no handheld record with pid. + */ + void setPCObjectId( const QString &conduit, recordid_t pid + , const QString &uid ); + + /** + * Sets the PC uid for the handheld record with pid. Does nothing when + * there is no handheld record with pid. + */ + void setHHObjectId( const QString &conduit, const QString &uid + , recordid_t pid ); + + /** + * Returns the PC uid for the handheld record with pid. Returns 0 when no + * pid exists for given uid. + */ + recordid_t getHHObjectId( const QString &conduit, const QString &uid ); + + /** + * Returns the HH pid for the PC record with uid. Returns an empty string + * when no uid exists for given pid. + */ + QString getHHObjectId( const QString &conduit, recordid_t pid ); + + /** + * Returns true when there is a uid set for given pid. The conduit itself + * must determine if the two objects are in sync if this function returns + * true. + */ + bool hasPCId( const QString &conduit, recordid_t pid ); + + /** + * Returns true when there is a pid set for given uid. The conduit itself + * must determine if the two objects are in sync if this function returns + * true. + */ + bool hasHHId( const QString &conduit, const QString &uid ); + + /** + * Sets the time that the two objects where last synced. The conduits + * should call this method (or the pid version) when two objects are synced. + * When the uid does not exist nothing happens. + */ + void setLastSyncTime( const QString &conduit, const QString &uid, + const QDateTime &date ); + + /** + * Sets the time that the two objects where last synced. The conduits + * should call this (or the uid version) method when two objects are synced. + * When the pid does not exist nothing happens. + */ + void setLastSyncTime( const QString &conduit, recordid_t pid + , const QDateTime &date ); + + /** + * Returns the date/time for the last time that the item with uid was + * synced. This date is set by: + * - setLastSyncTime (uid/pid) + * + * Returns a null datetime when the pid does not excist. + */ + QDateTime lastTimeSynced( const QString &conduit, const QString &uid ); + + /** + * Returns the date/time for the last time that the item with pid was + * synced. This date is set by: + * - setLastSyncTime (uid/pid) + * + * Returns a null datetime when the pid does not excist. + */ + QDateTime lastTimeSynced( const QString &conduit, recordid_t pid ); + +protected: + bool openDatasource( const QString &file ); + +private: + IDMapperPrivate *fP; +}; + +#endif diff --git a/kpilot/lib/idmapperxml.cc b/kpilot/lib/idmapperxml.cc new file mode 100644 index 000000000..a2a78a0c9 --- /dev/null +++ b/kpilot/lib/idmapperxml.cc @@ -0,0 +1,213 @@ +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "idmapperxml.h" + +#include "options.h" + +IDMapperXml::IDMapperXml( const QString &file ) : fFile(file) + , fCurrentMapping( 0l ) +{ +} + +IDMapperXml::~IDMapperXml() +{ + FUNCTIONSETUP; +} + +bool IDMapperXml::open() +{ + FUNCTIONSETUP; + + root = doc.createElement( CSL1("mappings") ); + QDomNode node = doc.createProcessingInstruction(CSL1("xml") + ,CSL1("version=\"1.0\" encoding=\"UTF-8\"")); + + doc.appendChild( node ); + doc.appendChild( root ); + + if( !fFile.exists() ) + { + DEBUGKPILOT << fname << ": Creating " << fFile.name() << endl; + + if( fFile.open( IO_ReadWrite ) ) + { + QTextStream out( &fFile ); + doc.save( out, 4 ); + fFile.close(); + return true; + } + else + { + DEBUGKPILOT << fname << ": Could not create " << fFile.name() << endl; + return false; + } + } + else + { + DEBUGKPILOT << fname << ": Parsing file " << fFile.name() << endl; + QXmlSimpleReader reader; + reader.setContentHandler( this ); + + // Make sure that the file is closed after parsing. + bool result = reader.parse( fFile ); + fFile.close(); + + return result; + } +} + +void IDMapperXml::save() +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname << ": Saving " << fMappings.count() + << " mappings..." << endl; + DEBUGKPILOT << fname << ": "; + + QValueList<IDMapping>::const_iterator it; + for ( it = fMappings.begin(); it != fMappings.end(); ++it ) + { + DEBUGKPILOT << "."; + + IDMapping mapping = (*it); + + DEBUGKPILOT << fname << ": " << mapping.conduit(); + + QDomElement mappingElement = doc.createElement( CSL1("mapping") ); + mappingElement.setAttribute( CSL1("conduit"), mapping.conduit() ); + + if( !mapping.uid().isNull() ) + { + QDomElement uidElement = doc.createElement( CSL1("uid") ); + uidElement.setAttribute( CSL1("value"), mapping.uid() ); + mappingElement.appendChild( uidElement ); + } + + if( mapping.pid() != 0 ) + { + QDomElement uidElement = doc.createElement( CSL1("pid") ); + uidElement.setAttribute( CSL1("value"), mapping.pid() ); + mappingElement.appendChild( uidElement ); + } + + if( !mapping.lastSyncTime().isNull() ) + { + QDomElement uidElement = doc.createElement( CSL1("pid") ); + uidElement.setAttribute( CSL1("value"), QString::number( mapping.pid() ) ); + mappingElement.appendChild( uidElement ); + } + + root.appendChild( mappingElement ); + } + + if( fFile.open( IO_ReadWrite ) ) + { + QTextStream out( &fFile ); + doc.save( out, 4 ); + fFile.close(); + + DEBUGKPILOT << endl << fname << ": finished saving." << endl; + } +} + +void IDMapperXml::addMapping( const IDMapping &mapping ) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname << ": " << mapping.conduit() << endl; + + fMappings.append( mapping ); + + DEBUGKPILOT << fname << ": " << fMappings.first().conduit() << endl; +} + +QValueList<IDMapping>& IDMapperXml::mappings() +{ + return fMappings; +} + +bool IDMapperXml::startElement( const QString &namespaceURI + , const QString &localName, const QString &qName + , const QXmlAttributes &attribs ) +{ + FUNCTIONSETUP; + Q_UNUSED(namespaceURI); + Q_UNUSED(localName); + + if( qName == CSL1("mapping") ) + { + QString conduit( attribs.value( CSL1("conduit") ) ); + + fCurrentMapping = new IDMapping( conduit ); + } + else if( qName == CSL1("uid") ) + { + fCurrentMapping->setUid( attribs.value( CSL1("value") ) ); + } + else if( qName == CSL1("pid") ) + { + fCurrentMapping->setPid( attribs.value( CSL1("value") ).toULong() ); + } + else if( qName == CSL1("lastsync") ) + { + // NOTE: this isn't very robuust! + // Parses only dates in the form: dd-mm-yyyy hh:mm:ss + QString date = attribs.value( CSL1("value") ); + int day = date.left(2).toInt(); + int month = date.mid(3,2).toInt(); + int year = date.mid(6, 4).toInt(); + + int hour = date.mid(11,2).toInt(); + int minute = date.mid(14,2).toInt(); + int second = date.mid(17,2).toInt(); + + QDate tmpDate = QDate( year, month, day ); + QTime tmpTime = QTime( hour, minute, second ); + + fCurrentMapping->setLastSyncTime( QDateTime( tmpDate, tmpTime ) ); + } + + return true; +} + +bool IDMapperXml::endElement( const QString &namespaceURI + , const QString &localName, const QString &qName ) +{ + FUNCTIONSETUP; + + Q_UNUSED(namespaceURI); + Q_UNUSED(localName); + Q_UNUSED(qName); + + if( qName == CSL1("mapping") ) + { + addMapping( *fCurrentMapping ); + delete fCurrentMapping; + fCurrentMapping = 0l; + } + + return true; +} diff --git a/kpilot/lib/idmapperxml.h b/kpilot/lib/idmapperxml.h new file mode 100644 index 000000000..455673888 --- /dev/null +++ b/kpilot/lib/idmapperxml.h @@ -0,0 +1,84 @@ +#ifndef _KPILOT_IDMAPPERXML_H +#define _KPILOT_IDMAPPERXML_H +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "idmapping.h" + +#include <qxml.h> +#include <qdom.h> +#include <qstring.h> +#include <qptrcollection.h> + +class IDMapperXml : public QXmlDefaultHandler +{ +public: + IDMapperXml( const QString &file ); + + ~IDMapperXml(); + + /** + * Opens and parses the file or creates a new one if the file does not exist. + */ + bool open(); + + /** + * Saves the current mappings to the xml-file. Note this function must be + * called after changes and before deleting the IDMapperXml object. Otherwise + * the changes won't be saved. + */ + void save(); + + /** + * Adds a mapping to the collection of mappings. + */ + void addMapping( const IDMapping &mapping ); + + /** + * Returns the collection of mappings. + */ + QValueList<IDMapping> &mappings(); + + /** + * Overloaded function to parse the xml file. + */ + bool startElement( const QString &namespaceURI, const QString &localName + , const QString &qName, const QXmlAttributes &attribs ); + + /** + * Overloaded function to parse the xml file. + */ + bool endElement( const QString &namespaceURI, const QString &localName + , const QString &qName ); + +private: + QFile fFile; + QDomDocument doc; + QDomElement root; + IDMapping *fCurrentMapping; + QValueList<IDMapping> fMappings; +}; + +#endif diff --git a/kpilot/lib/idmapping.cc b/kpilot/lib/idmapping.cc new file mode 100644 index 000000000..7a49e9e3e --- /dev/null +++ b/kpilot/lib/idmapping.cc @@ -0,0 +1,89 @@ +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "idmapping.h" + +IDMapping::IDMapping() +{ +} + +IDMapping::IDMapping( const QString &conduit ) +{ + fConduit = conduit; + fPid = 0; +} + +IDMapping::IDMapping( const IDMapping &m ) +{ + fConduit = m.fConduit; + fUid = m.fUid; + fPid = m.fPid; + fLastSync = m.fLastSync; +} + +IDMapping IDMapping::operator=( const IDMapping &m ) +{ + IDMapping local( m.fConduit ); + local.fUid = m.fUid; + local.fPid = m.fPid; + local.fLastSync = m.fLastSync; + + return local; +} + +void IDMapping::setUid( const QString &uid ) +{ + fUid = uid; +} + +void IDMapping::setPid( recordid_t pid ) +{ + fPid = pid; +} + +void IDMapping::setLastSyncTime( const QDateTime &datetime ) +{ + fLastSync = datetime; +} + +QString IDMapping::conduit() const +{ + return fConduit; +} + +QString IDMapping::uid() const +{ + return fUid; +} + +recordid_t IDMapping::pid() const +{ + return fPid; +} + +QDateTime IDMapping::lastSyncTime() const +{ + return fLastSync; +} diff --git a/kpilot/lib/idmapping.h b/kpilot/lib/idmapping.h new file mode 100644 index 000000000..acb17dc8a --- /dev/null +++ b/kpilot/lib/idmapping.h @@ -0,0 +1,66 @@ +#ifndef _KPILOT_IDMAPPING_H +#define _KPILOT_IDMAPPING_H +/* +** Copyright (C) 2006 Bertjan Broeksema <bbroeksema@bluebottle.com> +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "pi-macros.h" + +#include <qstring.h> +#include <qdatetime.h> + +class IDMapping +{ +public: + IDMapping(); + + IDMapping( const QString &conduit ); + + IDMapping( const IDMapping &m ); + + IDMapping operator=( const IDMapping &m ); + + void setUid( const QString &uid ); + + void setPid( recordid_t uid ); + + void setLastSyncTime( const QDateTime &datetime ); + + QString conduit() const; + + QString uid() const; + + recordid_t pid() const; + + QDateTime lastSyncTime() const; + +private: + QString fConduit; + QString fUid; + recordid_t fPid; + QDateTime fLastSync; +}; + +#endif diff --git a/kpilot/lib/kpilotdevicelink.cc b/kpilot/lib/kpilotdevicelink.cc new file mode 100644 index 000000000..55027d763 --- /dev/null +++ b/kpilot/lib/kpilotdevicelink.cc @@ -0,0 +1,966 @@ +/* KPilot + ** + ** Copyright (C) 1998-2001 by Dan Pilone + ** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> + ** Copyright (C) 2006-2007 Adriaan de Groot <groot@kde.org> + ** Copyright (C) 2007 Jason 'vanRijn' Kasper <vR@movingparts.net> + ** + */ + +/* + ** This program is free software; you can redistribute it and/or modify + ** it under the terms of the GNU Lesser General Public License as published by + ** the Free Software Foundation; either version 2.1 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU Lesser General Public License for more details. + ** + ** You should have received a copy of the GNU Lesser General Public License + ** along with this program in a file called COPYING; if not, write to + ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + ** MA 02110-1301, USA. + */ + +/* + ** Bug reports and questions can be sent to kde-pim@kde.org + */ + +#include "options.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <iostream> + +#include <pi-source.h> +#include <pi-socket.h> +#include <pi-dlp.h> +#include <pi-file.h> +#include <pi-buffer.h> + +#include <qdir.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qthread.h> +#include <qsocketnotifier.h> + +#include <kconfig.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kurl.h> +#include <kio/netaccess.h> + +#include "pilotUser.h" +#include "pilotSysInfo.h" +#include "pilotCard.h" +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" + +#include "kpilotlink.h" +#include "kpilotdevicelinkPrivate.moc" +#include "kpilotdevicelink.moc" + + +DeviceMap *DeviceMap::mThis = 0L; + + +static inline void startOpenTimer(DeviceCommThread *dev, QTimer *&t) +{ + if ( !t) + { + t = new QTimer(dev); + QObject::connect(t, SIGNAL(timeout()), dev, SLOT(openDevice())); + } + // just a single-shot timer. we'll know when to start it again... + t->start(1000, true); +} + +DeviceCommThread::DeviceCommThread(KPilotDeviceLink *d) : + QThread(), + fDone(true), + fHandle(d), + fOpenTimer(0L), + fSocketNotifier(0L), + fSocketNotifierActive(false), + fWorkaroundUSBTimer(0L), + fPilotSocket(-1), + fTempSocket(-1), + fAcceptedCount(0) +{ + FUNCTIONSETUP; +} + + +DeviceCommThread::~DeviceCommThread() +{ + FUNCTIONSETUPL(2); + close(); + KPILOT_DELETE(fWorkaroundUSBTimer); +} + +void DeviceCommThread::close() +{ + FUNCTIONSETUPL(2); + + KPILOT_DELETE(fWorkaroundUSBTimer); + KPILOT_DELETE(fOpenTimer); + KPILOT_DELETE(fSocketNotifier); + fSocketNotifierActive=false; + + if (fTempSocket != -1) + { + DEBUGKPILOT << fname + << ": device comm thread closing socket: [" + << fTempSocket << "]" << endl; + + pi_close(fTempSocket); + } + + if (fPilotSocket != -1) + { + DEBUGKPILOT << fname + << ": device comm thread closing socket: [" + << fPilotSocket << "]" << endl; + + pi_close(fPilotSocket); + } + + fTempSocket = (-1); + fPilotSocket = (-1); + + DeviceMap::self()->unbindDevice(link()->fRealPilotPath); +} + +void DeviceCommThread::reset() +{ + FUNCTIONSETUP; + + if (link()->fMessages->shouldPrint(Messages::OpenFailMessage)) + { + QApplication::postEvent(link(), new DeviceCommEvent(EventLogMessage, + i18n("Could not open device: %1 (will retry)") + .arg(link()->pilotPath() ))); + } + + link()->fMessages->reset(); + close(); + + // Timer already deleted by close() call. + startOpenTimer(this,fOpenTimer); + + link()->fLinkStatus = WaitingForDevice; +} + +/** + * This is an asyncronous process. We try to create a socket with the Palm + * and then bind to it (in open()). If we're able to do those 2 things, then + * we do 2 things: we set a timeout timer (which will tell us that X amount of + * time has transpired before we get into the meat of the sync transaction), and + * we also set up a QSocketNotifier, which will tell us when data is available + * to be read from the Palm socket. If we were unable to create a socket + * and/or bind to the Palm in this method, we'll start our timer again. + */ +void DeviceCommThread::openDevice() +{ + FUNCTIONSETUPL(2); + + bool deviceOpened = false; + + // This transition (from Waiting to Found) can only be + // taken once. + // + if (link()->fLinkStatus == WaitingForDevice) + { + link()->fLinkStatus = FoundDevice; + } + + if (link()->fMessages->shouldPrint(Messages::OpenMessage)) + { + QApplication::postEvent(link(), new DeviceCommEvent(EventLogMessage, + i18n("Trying to open device %1...") + .arg(link()->fPilotPath))); + } + + // if we're not supposed to be done, try to open the main pilot + // path... + if (!fDone && link()->fPilotPath.length() > 0) + { + DEBUGKPILOT << fname << ": Opening main pilot path: [" + << link()->fPilotPath << "]." << endl; + deviceOpened = open(link()->fPilotPath); + } + + // only try the temp device if our earlier attempt didn't work and the temp + // device is different than the main device, and it's a non-empty + // string + bool tryTemp = !deviceOpened && (link()->fTempDevice.length() > 0) && (link()->fPilotPath != link()->fTempDevice); + + // if we're not supposed to be done, and we should try the temp + // device, try the temp device... + if (!fDone && tryTemp) + { + DEBUGKPILOT << fname << ": Couldn't open main pilot path. " + << "Now trying temp device: [" + << link()->fTempDevice << "]." << endl; + deviceOpened = open(link()->fTempDevice); + } + + // if we couldn't connect, try to connect again... + if (!fDone && !deviceOpened) + { + startOpenTimer(this, fOpenTimer); + } +} + +bool DeviceCommThread::open(const QString &device) +{ + FUNCTIONSETUPL(2); + + int ret; + int e = 0; + QString msg; + + if (fTempSocket != -1) + { + pi_close(fTempSocket); + } + fTempSocket = (-1); + + link()->fRealPilotPath + = KStandardDirs::realFilePath(device.isEmpty() ? link()->fPilotPath : device); + + if ( !DeviceMap::self()->canBind(link()->fRealPilotPath) ) + { + msg = i18n("Already listening on that device"); + + WARNINGKPILOT << "Pilot Path: [" + << link()->fRealPilotPath << "] already connected." << endl; + WARNINGKPILOT << msg << endl; + + link()->fLinkStatus = PilotLinkError; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); + + return false; + } + + DEBUGKPILOT << fname << ": Trying to create socket." << endl; + + fTempSocket = pi_socket(PI_AF_PILOT, PI_SOCK_STREAM, PI_PF_DLP); + + if (fTempSocket < 0) + { + e = errno; + msg = i18n("Cannot create socket for communicating " + "with the Pilot (%1)").arg(errorMessage(e)); + DEBUGKPILOT << msg << endl; + DEBUGKPILOT << "(" << strerror(e) << ")" << endl; + + link()->fLinkStatus = PilotLinkError; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); + + return false; + } + + DEBUGKPILOT << fname << ": Got socket: [" << fTempSocket << "]" << endl; + + link()->fLinkStatus = CreatedSocket; + + DEBUGKPILOT << fname << ": Binding to path: [" + << link()->fRealPilotPath << "]" << endl; + + ret = pi_bind(fTempSocket, QFile::encodeName(link()->fRealPilotPath)); + + if (ret < 0) + { + DEBUGKPILOT << fname + << ": pi_bind error: [" + << strerror(errno) << "]" << endl; + + e = errno; + msg = i18n("Cannot open Pilot port \"%1\". ").arg(link()->fRealPilotPath); + + DEBUGKPILOT << msg << endl; + DEBUGKPILOT << "(" << strerror(e) << ")" << endl; + + link()->fLinkStatus = PilotLinkError; + + if (link()->fMessages->shouldPrint(Messages::OpenFailMessage)) + { + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, msg)); + } + + return false; + } + + link()->fLinkStatus = DeviceOpen; + DeviceMap::self()->bindDevice(link()->fRealPilotPath); + + fSocketNotifier = new QSocketNotifier(fTempSocket, + QSocketNotifier::Read, this); + QObject::connect(fSocketNotifier, SIGNAL(activated(int)), + this, SLOT(acceptDevice())); + fSocketNotifierActive=true; + + /** + * We _always_ want to set a maximum amount of time that we will wait + * for the sync process to start. In the case where our user + * has told us that he has a funky USB device, set the workaround timeout + * for shorter than normal. + */ + int timeout=20000; + if (link()->fWorkaroundUSB) + { + timeout=5000; + } + + fWorkaroundUSBTimer = new QTimer(this); + connect(fWorkaroundUSBTimer, SIGNAL(timeout()), this, SLOT(workaroundUSB())); + fWorkaroundUSBTimer->start(timeout, true); + + return true; +} + +/** + * We've been notified by our QSocketNotifier that we have data available on the + * socket. Try to go through the remaining steps of the connnection process. + * Note: If we return at all from this before the very end without a successful + * connection, we need to make sure we restart our connection open timer, otherwise + * it won't be restarted. + */ +void DeviceCommThread::acceptDevice() +{ + FUNCTIONSETUP; + + int ret; + + /** + * Our socket notifier should be the only reason that we end up here. + * If we're here without him being active, we have a problem. Try to clean + * up and get out. + */ + if (!fSocketNotifierActive) + { + if (!fAcceptedCount) + { + kdWarning() << k_funcinfo << ": Accidentally in acceptDevice()" + << endl; + } + fAcceptedCount++; + if (fAcceptedCount>10) + { + // Damn the torpedoes + KPILOT_DELETE(fSocketNotifier); + } + return; + } + + if (fSocketNotifier) + { + // fSocketNotifier->setEnabled(false); + fSocketNotifierActive=false; + KPILOT_DELETE(fSocketNotifier); + } + + DEBUGKPILOT << fname << ": Found connection on device: [" + << link()->pilotPath().latin1() << "]." <<endl; + + DEBUGKPILOT << fname + << ": Current status: [" + << link()->statusString() + << "] and socket: [" << fTempSocket << "]" << endl; + + ret = pi_listen(fTempSocket, 1); + if (ret < 0) + { + char *s = strerror(errno); + + WARNINGKPILOT << "pi_listen returned: [" << s << "]" << endl; + + // Presumably, strerror() returns things in + // local8Bit and not latin1. + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, + i18n("Cannot listen on Pilot socket (%1)"). + arg(QString::fromLocal8Bit(s)))); + reset(); + return; + } + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, QString::null, 10)); + + DEBUGKPILOT << fname << + ": Listening to pilot. Now trying accept..." << endl; + + int timeout = 20; + fPilotSocket = pi_accept_to(fTempSocket, 0, 0, timeout); + + if (fPilotSocket < 0) + { + char *s = strerror(errno); + + WARNINGKPILOT << "pi_accept returned: [" << s << "]" << endl; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Cannot accept Pilot (%1)") + .arg(QString::fromLocal8Bit(s)))); + + link()->fLinkStatus = PilotLinkError; + reset(); + return; + } + + DEBUGKPILOT << fname << ": Link accept done." << endl; + + if ((link()->fLinkStatus != DeviceOpen) || (fPilotSocket == -1)) + { + link()->fLinkStatus = PilotLinkError; + WARNINGKPILOT << "Already connected or unable to connect!" << endl; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, i18n("Cannot accept Pilot (%1)") + .arg(i18n("already connected")))); + + reset(); + return; + } + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, QString::null, 30)); + + DEBUGKPILOT << fname << ": doing dlp_ReadSysInfo..." << endl; + + struct SysInfo sys_info; + if (dlp_ReadSysInfo(fPilotSocket, &sys_info) < 0) + { + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, + i18n("Unable to read system information from Pilot"))); + + link()->fLinkStatus=PilotLinkError; + reset(); + return; + } + else + { + DEBUGKPILOT << fname << ": dlp_ReadSysInfo successful..." << endl; + + KPILOT_DELETE(link()->fPilotSysInfo); + link()->fPilotSysInfo = new KPilotSysInfo(&sys_info); + DEBUGKPILOT << fname + << ": RomVersion: [" << link()->fPilotSysInfo->getRomVersion() + << "] Locale: [" << link()->fPilotSysInfo->getLocale() + << "] Product: [" << link()->fPilotSysInfo->getProductID() + << "]" << endl; + } + + // If we've made it this far, make sure our USB workaround timer doesn't fire! + fWorkaroundUSBTimer->stop(); + KPILOT_DELETE(fWorkaroundUSBTimer); + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, QString::null, 60)); + + KPILOT_DELETE(link()->fPilotUser); + link()->fPilotUser = new KPilotUser; + + DEBUGKPILOT << fname << ": doing dlp_ReadUserInfo..." << endl; + + /* Ask the pilot who it is. And see if it's who we think it is. */ + dlp_ReadUserInfo(fPilotSocket, link()->fPilotUser->data()); + + QString n = link()->getPilotUser().name(); + DEBUGKPILOT << fname + << ": Read user name: [" << n << "]" << endl; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, i18n("Checking last PC..."), 90)); + + /* Tell user (via Pilot) that we are starting things up */ + if ((ret=dlp_OpenConduit(fPilotSocket)) < 0) + { + DEBUGKPILOT << fname + << ": dlp_OpenConduit returned: [" << ret << "]" << endl; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogError, + i18n("Could not read user information from the Pilot. " + "Perhaps you have a password set on the device?"))); + + } + link()->fLinkStatus = AcceptedDevice; + + QApplication::postEvent(link(), new DeviceCommEvent(EventLogProgress, QString::null, 100)); + + DeviceCommEvent * ev = new DeviceCommEvent(EventDeviceReady); + ev->setCurrentSocket(fPilotSocket); + QApplication::postEvent(link(), ev); + +} + +void DeviceCommThread::workaroundUSB() +{ + FUNCTIONSETUP; + + reset(); +} + +void DeviceCommThread::run() +{ + FUNCTIONSETUP; + fDone = false; + + startOpenTimer(this, fOpenTimer); + + int sleepBetweenPoll = 2; + // keep the thread alive until we're supposed to be done + while (!fDone) + { + QThread::sleep(sleepBetweenPoll); + } + + close(); + // now sleep one last bit to make sure the pthread inside + // pilot-link (potentially, if it's libusb) is done before we exit + QThread::sleep(1); + + DEBUGKPILOT << fname << ": comm thread now done..." << endl; +} + +KPilotDeviceLink::KPilotDeviceLink(QObject * parent, const char *name, + const QString &tempDevice) : + KPilotLink(parent, name), fLinkStatus(Init), fWorkaroundUSB(false), + fPilotSocket(-1), fTempDevice(tempDevice), fMessages(new Messages(this)), fDeviceCommThread(0L) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname + << ": Pilot-link version: [" << PILOT_LINK_NUMBER + << "]" << endl; +} + +KPilotDeviceLink::~KPilotDeviceLink() +{ + FUNCTIONSETUP; + close(); + KPILOT_DELETE(fPilotSysInfo); + KPILOT_DELETE(fPilotUser); + KPILOT_DELETE(fMessages); +} + +/* virtual */bool KPilotDeviceLink::isConnected() const +{ + return fLinkStatus == AcceptedDevice; +} + +/* virtual */bool KPilotDeviceLink::event(QEvent *e) +{ + FUNCTIONSETUP; + + bool handled = false; + + if ((int)e->type() == EventDeviceReady) + { + DeviceCommEvent* t = static_cast<DeviceCommEvent*>(e); + fPilotSocket = t->currentSocket(); + emit deviceReady( this); + handled = true; + } + else if ((int)e->type() == EventLogMessage) + { + DeviceCommEvent* t = static_cast<DeviceCommEvent*>(e); + emit logMessage(t->message()); + handled = true; + } + else if ((int)e->type() == EventLogError) + { + DeviceCommEvent* t = static_cast<DeviceCommEvent*>(e); + emit logError(t->message()); + handled = true; + } + else if ((int)e->type() == EventLogProgress) + { + DeviceCommEvent* t = static_cast<DeviceCommEvent*>(e); + emit logProgress(t->message(), t->progress()); + handled = true; + } + else + { + handled = KPilotLink::event(e); + } + + return handled; +} + +void KPilotDeviceLink::stopCommThread() +{ + FUNCTIONSETUP; + if (fDeviceCommThread) + { + fDeviceCommThread->setDone(true); + + // try to wait for our thread to finish, but don't + // block the main thread forever + if (fDeviceCommThread->running()) + { + DEBUGKPILOT << fname + << ": comm thread still running. " + << "waiting for it to complete." << endl; + bool done = fDeviceCommThread->wait(5000); + if (!done) + { + DEBUGKPILOT << fname + << ": comm thread still running " + << "after wait(). " + << "going to have to terminate it." + << endl; + // not normally to be done, but we must make sure + // that this device doesn't come back alive + fDeviceCommThread->terminate(); + fDeviceCommThread->wait(); + } + } + + fDeviceCommThread->close(); + + KPILOT_DELETE(fDeviceCommThread); + } +} + +void KPilotDeviceLink::close() +{ + FUNCTIONSETUP; + + stopCommThread(); + + fPilotSocket = (-1); +} + +void KPilotDeviceLink::reset(const QString & dP) +{ + FUNCTIONSETUP; + + fLinkStatus = Init; + + // Release all resources + // + close(); + fPilotPath = QString::null; + + fPilotPath = dP; + if (fPilotPath.isEmpty()) + fPilotPath = fTempDevice; + if (fPilotPath.isEmpty()) + return; + + reset(); +} + +void KPilotDeviceLink::startCommThread() +{ + FUNCTIONSETUP; + + stopCommThread(); + + if (fTempDevice.isEmpty() && pilotPath().isEmpty()) + { + WARNINGKPILOT << "No point in trying empty device." + << endl; + + QString msg = i18n("The Pilot device is not configured yet."); + WARNINGKPILOT << msg << endl; + + fLinkStatus = PilotLinkError; + + emit logError(msg); + return; + } + + fDeviceCommThread = new DeviceCommThread(this); + fDeviceCommThread->start(); +} + +void KPilotDeviceLink::reset() +{ + FUNCTIONSETUP; + + fMessages->reset(); + close(); + + checkDevice(); + + fLinkStatus = WaitingForDevice; + + startCommThread(); +} + +void KPilotDeviceLink::checkDevice() +{ + // If the device exists yet doesn't have the right + // permissions, complain and then continue anyway. + // + QFileInfo fi(fPilotPath); + if (fi.exists()) + { + // If it exists, it ought to be RW already. + // + if (!(fi.isReadable() && fi.isWritable())) + { + emit logError(i18n("Pilot device %1 is not read-write.") + .arg(fPilotPath)); + } + } + else + { + // It doesn't exist, mention this in the log + // (relevant as long as we use only one device type) + // + emit + logError(i18n("Pilot device %1 does not exist. " + "Probably it is a USB device and will appear during a HotSync.") + .arg(fPilotPath)); + // Suppress all normal and error messages about opening the device. + fMessages->block(Messages::OpenMessage | Messages::OpenFailMessage, + true); + } +} + +void KPilotDeviceLink::setTempDevice(const QString &d) +{ + fTempDevice = d; + DeviceMap::self()->bindDevice(fTempDevice); +} + +/* virtual */bool KPilotDeviceLink::tickle() +{ + // No FUNCTIONSETUP here because it may be called from + // a separate thread. + return pi_tickle(pilotSocket()) >= 0; +} + +/* virtual */void KPilotDeviceLink::addSyncLogEntryImpl(const QString &entry) +{ + dlp_AddSyncLogEntry(fPilotSocket, + const_cast<char *>((const char *)Pilot::toPilot(entry))); +} + +bool KPilotDeviceLink::installFile(const QString & f, const bool deleteFile) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname << ": Installing file " << f << endl; + + if (!QFile::exists(f)) + return false; + + char buffer[PATH_MAX]; + memset(buffer, 0, PATH_MAX); + strlcpy(buffer, QFile::encodeName(f), PATH_MAX); + struct pi_file *pf = pi_file_open(buffer); + + if (!f) + { + WARNINGKPILOT << "Cannot open file " << f << endl; + emit logError(i18n + ("<qt>Cannot install the file "%1".</qt>"). + arg(f)); + return false; + } + + if (pi_file_install(pf, fPilotSocket, 0, 0L) < 0) + { + WARNINGKPILOT << "Cannot pi_file_install " << f << endl; + emit logError(i18n + ("<qt>Cannot install the file "%1".</qt>"). + arg(f)); + return false; + } + + pi_file_close(pf); + if (deleteFile) + QFile::remove(f); + + return true; +} + +int KPilotDeviceLink::openConduit() +{ + return dlp_OpenConduit(fPilotSocket); +} + +QString KPilotDeviceLink::statusString(LinkStatus l) +{ + QString s= CSL1("KPilotDeviceLink="); + + switch (l) + { + case Init: + s.append(CSL1("Init")); + break; + case WaitingForDevice: + s.append(CSL1("WaitingForDevice")); + break; + case FoundDevice: + s.append(CSL1("FoundDevice")); + break; + case CreatedSocket: + s.append(CSL1("CreatedSocket")); + break; + case DeviceOpen: + s.append(CSL1("DeviceOpen")); + break; + case AcceptedDevice: + s.append(CSL1("AcceptedDevice")); + break; + case SyncDone: + s.append(CSL1("SyncDone")); + break; + case PilotLinkError: + s.append(CSL1("PilotLinkError")); + break; + case WorkaroundUSB: + s.append(CSL1("WorkaroundUSB")); + break; + } + + return s; +} + +QString KPilotDeviceLink::statusString() const +{ + return statusString(status() ); +} + +void KPilotDeviceLink::endSync(EndOfSyncFlags f) +{ + FUNCTIONSETUP; + + if (UpdateUserInfo == f) + { + getPilotUser().setLastSyncPC((unsigned long) gethostid()); + getPilotUser().setLastSyncDate(time(0)); + + DEBUGKPILOT << fname << ": Writing username " << getPilotUser().name() << endl; + + dlp_WriteUserInfo(pilotSocket(), getPilotUser().data()); + addSyncLogEntry(i18n("End of HotSync\n")); + } + dlp_EndOfSync(pilotSocket(), 0); + KPILOT_DELETE(fPilotSysInfo); + KPILOT_DELETE(fPilotUser); +} + +int KPilotDeviceLink::getNextDatabase(int index, struct DBInfo *dbinfo) +{ + FUNCTIONSETUP; + + pi_buffer_t buf = { 0, 0, 0 }; + int r = dlp_ReadDBList(pilotSocket(), 0, dlpDBListRAM, index, &buf); + if (r >= 0) + { + memcpy(dbinfo, buf.data, sizeof(struct DBInfo)); + } + return r; +} + +// Find a database with the given name. Info about the DB is stored into dbinfo (e.g. to be used later on with retrieveDatabase). +int KPilotDeviceLink::findDatabase(const char *name, struct DBInfo *dbinfo, + int index, unsigned long type, unsigned long creator) +{ + FUNCTIONSETUP; + return dlp_FindDBInfo(pilotSocket(), 0, index, const_cast<char *>(name), + type, creator, dbinfo); +} + +bool KPilotDeviceLink::retrieveDatabase(const QString &fullBackupName, + DBInfo *info) +{ + FUNCTIONSETUP; + + if (fullBackupName.isEmpty() || !info) + { + // Don't even bother trying to convert or retrieve. + return false; + } + + DEBUGKPILOT << fname << ": Writing DB <" << info->name << "> " + << " to " << fullBackupName << endl; + + QCString encodedName = QFile::encodeName(fullBackupName); + struct pi_file *f = pi_file_create(encodedName, info); + + if (!f) + { + WARNINGKPILOT << "Failed, unable to create file" << endl; + return false; + } + + if (pi_file_retrieve(f, pilotSocket(), 0, 0L) < 0) + { + WARNINGKPILOT << "Failed, unable to back up database" << endl; + + pi_file_close(f); + return false; + } + + pi_file_close(f); + return true; +} + +KPilotLink::DBInfoList KPilotDeviceLink::getDBList(int cardno, int flags) +{ + bool cont=true; + DBInfoList dbs; + int index=0; + while (cont) + { + pi_buffer_t buf = { 0, 0, 0 }; + pi_buffer_clear(&buf); + // DBInfo*dbi=new DBInfo(); + if (dlp_ReadDBList(pilotSocket(), cardno, flags | dlpDBListMultiple, + index, &buf)<0) + { + cont=false; + } + else + { + DBInfo db_n; + DBInfo *db_it = (DBInfo *)buf.data; + int info_count = buf.used / sizeof(struct DBInfo); + + while (info_count>0) + { + memcpy(&db_n, db_it, sizeof(struct DBInfo)); + ++db_it; + info_count--; + dbs.append(db_n); + } + index=db_n.index+1; + } + } + return dbs; +} + +const KPilotCard *KPilotDeviceLink::getCardInfo(int card) +{ + KPilotCard *cardinfo=new KPilotCard(); + if (dlp_ReadStorageInfo(pilotSocket(), card, cardinfo->cardInfo())<0) + { + WARNINGKPILOT << "Could not get info for card " << card << endl; + + KPILOT_DELETE(cardinfo); + return 0L; + } + return cardinfo; +} + +PilotDatabase *KPilotDeviceLink::database(const QString &name) +{ + return new PilotSerialDatabase( this, name ); +} + +PilotDatabase *KPilotDeviceLink::database(const DBInfo *info) +{ + return new PilotSerialDatabase( this, info ); +} + diff --git a/kpilot/lib/kpilotdevicelink.h b/kpilot/lib/kpilotdevicelink.h new file mode 100644 index 000000000..d2527aee4 --- /dev/null +++ b/kpilot/lib/kpilotdevicelink.h @@ -0,0 +1,220 @@ +#ifndef _KPILOT_KPILOTDEVICELINK_H +#define _KPILOT_KPILOTDEVICELINK_H +/* +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "kpilotlink.h" + +class QThread; + +class DeviceMap; ///< Globally tracks all devices that have a link assigned +class Messages; ///< Tracks which messages have been printed +class DeviceCommThread; ///< Thread for doing all palm device communications + +/** +* The link behaves like a state machine most of the time: +* it waits for the actual device to become available, and +* then becomes ready to handle syncing. +*/ +enum LinkStatus { + Init, + WaitingForDevice, + FoundDevice, + CreatedSocket, + DeviceOpen, + AcceptedDevice, + SyncDone, + PilotLinkError, + WorkaroundUSB +} ; + +/** +* Custom events we can be handling... +*/ +enum DeviceCustomEvents { + EventLogMessage = QEvent::User + 777, + EventLogError, + EventLogProgress, + EventDeviceReady +}; + +/** +* Definition of the device link class for physical +* handheld devices, which communicate with the PC +* using DLP / SLP via the pilot-link library. +*/ +class KDE_EXPORT KPilotDeviceLink : public KPilotLink +{ +friend class PilotSerialDatabase; +friend class DeviceCommThread; + +Q_OBJECT + +public: + /** + * Constructor. Creates a link that can sync to a physical handheld. + * Call reset() on it to start looking for a device. + * + * @param parent Parent object. + * @param name Name of this object. + * @param tempDevice Path to device node to use as an alternative + * to the "normal" one set by KPilot. + */ + KPilotDeviceLink( QObject *parent = 0, + const char *name = 0, + const QString &tempDevice = QString::null ); + + /** + * Destructor. This rudely ends the communication with the handheld. + * It is best to call endOfSync() or finishSync() before destroying + * the device. + */ + virtual ~KPilotDeviceLink(); + + /** + * Get the status (state enum) of this link. + * @return The LinkStatus enum for the link's current state. + */ + LinkStatus status() const + { + return fLinkStatus; + } + + /** Get a human-readable string for the given status @p l. */ + static QString statusString(LinkStatus l); + + // The following API is the actual implementation of + // the KPilotLink API, for documentation see that file. + // + virtual QString statusString() const; + virtual bool isConnected() const; + virtual void reset( const QString & ); + virtual void close(); + virtual void reset(); + virtual bool event(QEvent *e); + virtual bool tickle(); + virtual const KPilotCard *getCardInfo(int card); + virtual void endSync( EndOfSyncFlags f ); + virtual int openConduit(); + virtual int getNextDatabase(int index,struct DBInfo *); + virtual int findDatabase(const char *name, struct DBInfo*, + int index=0, unsigned long type=0, unsigned long creator=0); + virtual bool retrieveDatabase(const QString &path, struct DBInfo *db); + virtual DBInfoList getDBList(int cardno=0, int flags=dlpDBListRAM); + virtual PilotDatabase *database( const QString &name ); + virtual PilotDatabase *database( const DBInfo *info ); + +protected: + virtual bool installFile(const QString &, const bool deleteFile); + virtual void addSyncLogEntryImpl( const QString &s ); + virtual int pilotSocket() const + { + return fPilotSocket; + } + + +private: + LinkStatus fLinkStatus; + + +public: + + /** + * Special-cases. Call this after a reset to set device- + * specific workarounds; the only one currently known + * is the Zire 31/72 T5 quirk of doing a non-HotSync + * connect when it's switched on. + */ + void setWorkarounds(bool usb) + { + fWorkaroundUSB = usb; + } + + /** + * Sets an additional device, which should be tried as fallback. + * Useful for hotplug enviroments, this device is used @em once + * for accepting a connection. + */ + void setTempDevice( const QString &device ); + + /** + * Sets the device to use. Used by probe dialog, since we know + * what device to use, but we don't want to start the detection + * immediately. + */ + void setDevice( const QString &device ) + { + fPilotPath = device; + } + + +protected: + /** Should we work around the Zire31/72 quirk? @see setWorkarounds() */ + bool fWorkaroundUSB; + + + /** + * Check for device permissions and existence, emitting + * warnings for weird situations. This is primarily intended + * to inform the user. + */ + void checkDevice(); + +protected: + /** + * Path with resolved symlinks, to prevent double binding + * to the same device. + */ + QString fRealPilotPath; + + /** + * Pilot-link library handles for the device once it's opened. + */ + int fPilotSocket; + QString fTempDevice; + + /** + * Handle cases where we can't accept or open the device, + * and data remains available on the pilot socket. + */ + int fAcceptedCount; + + /** + * Start/Stop our device communication thread. + */ + void startCommThread(); + void stopCommThread(); + +protected: + Messages *fMessages; + DeviceCommThread *fDeviceCommThread; +} ; + +#endif + diff --git a/kpilot/lib/kpilotdevicelinkPrivate.h b/kpilot/lib/kpilotdevicelinkPrivate.h new file mode 100644 index 000000000..be2bbda35 --- /dev/null +++ b/kpilot/lib/kpilotdevicelinkPrivate.h @@ -0,0 +1,330 @@ +#ifndef _KPILOT_KPILOTDEVICELINKPRIVATE_H +#define _KPILOT_KPILOTDEVICELINKPRIVATE_H +/* +** +** Copyright (C) 2007 by Jason 'vanRijn' Kasper <vR@movingparts.net> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <errno.h> + +#include <qstringlist.h> +#include <qthread.h> + +#include "kpilotdevicelink.h" +#include "options.h" + +class QTimer; +class QSocketNotifier; + +// singleton helper class +class DeviceMap +{ +public: + static DeviceMap *self() + { + if (!mThis) + mThis = new DeviceMap(); + return mThis; + } + + bool canBind(const QString &device) + { + FUNCTIONSETUPL(5); + DEBUGKPILOT << fname << ": device: [" + << device << "]" << endl; + + showList(); + return !mBoundDevices.contains(device); + } + + void bindDevice(const QString &device) + { + FUNCTIONSETUPL(5); + DEBUGKPILOT << fname << ": device: [" + << device << "]" << endl; + + mBoundDevices.append(device); + showList(); + } + + void unbindDevice(const QString &device) + { + FUNCTIONSETUPL(5); + DEBUGKPILOT << fname << ": device: [" + << device << "]" << endl; + + mBoundDevices.remove(device); + showList(); + } + +protected: + DeviceMap() + { + mBoundDevices.clear(); + } + ~DeviceMap() + { + } + + QStringList mBoundDevices; + static DeviceMap *mThis; + +private: + void showList() const + { + FUNCTIONSETUPL(5); + + if ( !(mBoundDevices.count() > 0)) + return; + + DEBUGKPILOT << fname << ": Bound devices: [" + << ((mBoundDevices.count() > 0) ? + mBoundDevices.join(CSL1(", ")) : CSL1("<none>")) + << "]" << endl; + } +}; + +class Messages +{ +public: + Messages(KPilotDeviceLink *parent) : + fDeviceLink(parent) + { + reset(); + } + + void reset() + { + messages = 0; + messagesMask = ~messageIsError; // Never block errors + } + + void block(unsigned int m, bool force=false) + { + if (force) + { + // Force blocking this message, even if it's an error msg. + messages |= m; + } + else + { + messages |= (m & messagesMask); + } + } + + /** + * Some messages are only printed once and are suppressed + * after that. These are indicated by flag bits in + * messages. The following enum is a bitfield. + */ + enum + { + OpenMessage=1, ///< Trying to open device .. + OpenFailMessage=2 ///< Failed to open device ... + }; + int messages; + int messagesMask; + static const int messageIsError = 0; + + /** Determines whether message @p s which has an id of @p msgid (one of + * the enum values mentioned above) should be printed, which is only if that + * message has not been suppressed through messagesMask. + * If return is true, this method also adds it to the messagesMask. + */ + bool shouldPrint(int msgid) + { + if (!(messages & msgid)) + { + block(msgid); + return true; + } + else + { + return false; + } + } + +protected: + KPilotDeviceLink *fDeviceLink; +}; + +class DeviceCommEvent : public QEvent +{ +public: + DeviceCommEvent(DeviceCustomEvents type, QString msg = QString::null, + int progress = 0) : + QEvent( (QEvent::Type)type ), fMessage(msg), fProgress(progress), + fPilotSocket(-1) + { + } + QString message() const + { + return fMessage; + } + int progress() + { + return fProgress; + } + + inline void setCurrentSocket(int i) + { + fPilotSocket = i; + } + + inline int currentSocket() + { + return fPilotSocket; + } +private: + QString fMessage; + int fProgress; + /** + * Pilot-link library handles for the device once it's opened. + */ + int fPilotSocket; +}; + +/** Class that handles all device communications. We do this + in a different thread so that we do not block the main Qt + Event thread (similar to Swing's AWT event dispatch thread). + */ + +class DeviceCommThread : public QObject, public QThread +{ +friend class KPilotDeviceLink; + +Q_OBJECT + +public: + DeviceCommThread(KPilotDeviceLink *d); + virtual ~DeviceCommThread(); + + virtual void run(); + + void setDone(bool b) + { + FUNCTIONSETUP; + fDone = b; + } + +protected: + + void close(); + + void reset(); + + /** + * Does the low-level opening of the device and handles the + * pilot-link library initialisation. + */ + bool open(const QString &device = QString::null); + +protected slots: + /** + * Attempt to open the device. Called regularly to check + * if the device exists (to handle USB-style devices). + */ + void openDevice(); + + /** + * Called when the device is opened *and* activity occurs on the + * device. This indicates the beginning of a hotsync. + */ + void acceptDevice(); + + /** + * This slot fires whenever we've been trying to establish a hotsync with + * the device for longer than a given amount of time. When this slot is + * fired, we will tear down the communications process and start over again. + */ + void workaroundUSB(); + +private: + volatile bool fDone; + + KPilotDeviceLink *fHandle; + inline KPilotDeviceLink *link() + { + if (fHandle) + { + return fHandle; + } + else + { + FUNCTIONSETUP; + WARNINGKPILOT << "Link asked for, but either I'm " + << "done or I don't have a valid handle. " + << "Shutting down comm thread." << endl; + QThread::exit(); + return 0; + } + } + + /** + * Timers and Notifiers for detecting activity on the device. + */ + QTimer *fOpenTimer; + QSocketNotifier *fSocketNotifier; + bool fSocketNotifierActive; + + /** Timer used to check for a badly-connected Z31/72 */ + QTimer *fWorkaroundUSBTimer; + + /** + * Pilot-link library handles for the device once it's opened. + */ + int fPilotSocket; + int fTempSocket; + + inline QString errorMessage(int e) + { + switch (e) + { + case ENOENT: + return i18n(" The port does not exist."); + break; + case ENODEV: + return i18n(" There is no such device."); + break; + case EPERM: + return i18n(" You do not have permission to open the " + "Pilot device."); + break; + default: + return i18n(" Check Pilot path and permissions."); + } + } + + /** + * Handle cases where we can't accept or open the device, + * and data remains available on the pilot socket. + */ + int fAcceptedCount; + +}; + + +#endif + diff --git a/kpilot/lib/kpilotlib.kcfg b/kpilot/lib/kpilotlib.kcfg new file mode 100644 index 000000000..09e829216 --- /dev/null +++ b/kpilot/lib/kpilotlib.kcfg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <kcfgfile name="kpilotrc"/> + <group name="Notification Messages"> + </group> +</kcfg> diff --git a/kpilot/lib/kpilotlibSettings.kcfgc b/kpilot/lib/kpilotlibSettings.kcfgc new file mode 100644 index 000000000..d3b0d1352 --- /dev/null +++ b/kpilot/lib/kpilotlibSettings.kcfgc @@ -0,0 +1,7 @@ +File=kpilotlib.kcfg +ClassName=KPilotLibSettings +Singleton=true +ItemAccessors=true +Mutators=true +GlobalEnums=true +SetUserTexts=true diff --git a/kpilot/lib/kpilotlink.cc b/kpilot/lib/kpilotlink.cc new file mode 100644 index 000000000..9c0b85ee9 --- /dev/null +++ b/kpilot/lib/kpilotlink.cc @@ -0,0 +1,272 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006-2007 Adriaan de Groot <groot@kde.org> +** Copyright (C) 2007 Jason 'vanRijn' Kasper <vR@movingparts.net> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + + + +#include <sys/stat.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <iostream> + +#include <pi-source.h> +#include <pi-socket.h> +#include <pi-dlp.h> +#include <pi-file.h> +#include <pi-buffer.h> + +#include <qdir.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qthread.h> + +#include <kconfig.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kurl.h> +#include <kio/netaccess.h> + +#include "pilotUser.h" +#include "pilotSysInfo.h" +#include "pilotCard.h" +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" + +#include "kpilotlink.moc" + +/** Class that handles periodically tickling the handheld through +* the virtual tickle() method; deals with cancels through the +* shared fDone variable. +*/ +class TickleThread : public QThread +{ +public: + TickleThread(KPilotLink *d, bool *done, int timeout) : + QThread(), + fHandle(d), + fDone(done), + fTimeout(timeout) + { }; + virtual ~TickleThread(); + + virtual void run(); + + static const int ChecksPerSecond = 5; + static const int SecondsPerTickle = 5; + +private: + KPilotLink *fHandle; + bool *fDone; + int fTimeout; +} ; + +TickleThread::~TickleThread() +{ +} + +void TickleThread::run() +{ + FUNCTIONSETUP; + int subseconds = ChecksPerSecond; + int ticktock = SecondsPerTickle; + int timeout = fTimeout; + DEBUGKPILOT << fname << ": Running for " + << timeout << " seconds." << endl; + DEBUGKPILOT << fname << ": Done @" << (void *) fDone << endl; + + while (!(*fDone)) + { + QThread::msleep(1000/ChecksPerSecond); + if (!(--subseconds)) + { + if (timeout) + { + if (!(--timeout)) + { + QApplication::postEvent(fHandle, new QEvent(static_cast<QEvent::Type>(KPilotLink::EventTickleTimeout))); + break; + } + } + subseconds=ChecksPerSecond; + if (!(--ticktock)) + { + ticktock=SecondsPerTickle; + fHandle->tickle(); + } + } + } +} + + + + + + + + + +KPilotLink::KPilotLink( QObject *parent, const char *name ) : + QObject( parent, name ), + fPilotPath(QString::null), + fPilotUser(0L), + fPilotSysInfo(0L), + fTickleDone(true), + fTickleThread(0L) + +{ + FUNCTIONSETUP; + + fPilotUser = new KPilotUser(); + strncpy( fPilotUser->data()->username, "Henk Westbroek", + sizeof(fPilotUser->data()->username)-1); + fPilotUser->setLastSuccessfulSyncDate( 1139171019 ); + + fPilotSysInfo = new KPilotSysInfo(); + memset(fPilotSysInfo->sysInfo()->prodID, 0, + sizeof(fPilotSysInfo->sysInfo()->prodID)); + strncpy(fPilotSysInfo->sysInfo()->prodID, "LocalLink", + sizeof(fPilotSysInfo->sysInfo()->prodID)-1); + fPilotSysInfo->sysInfo()->prodIDLength = + strlen(fPilotSysInfo->sysInfo()->prodID); +} + +KPilotLink::~KPilotLink() +{ + FUNCTIONSETUP; + KPILOT_DELETE(fPilotUser); + KPILOT_DELETE(fPilotSysInfo); +} + +/* virtual */ bool KPilotLink::event(QEvent *e) +{ + if ((int)e->type() == EventTickleTimeout) + { + stopTickle(); + emit timeout(); + return true; + } + else return QObject::event(e); +} + +/* +Start a tickle thread with the indicated timeout. +*/ +void KPilotLink::startTickle(unsigned int timeout) +{ + FUNCTIONSETUP; + + Q_ASSERT(fTickleDone); + + /* + ** We've told the thread to finish up, but it hasn't + ** done so yet - so wait for it to do so, should be + ** only 200ms at most. + */ + if (fTickleDone && fTickleThread) + { + fTickleThread->wait(); + KPILOT_DELETE(fTickleThread); + } + + DEBUGKPILOT << fname << ": Done @" << (void *) (&fTickleDone) << endl; + + fTickleDone = false; + fTickleThread = new TickleThread(this,&fTickleDone,timeout); + fTickleThread->start(); +} + +void KPilotLink::stopTickle() +{ + FUNCTIONSETUP; + fTickleDone = true; + if (fTickleThread) + { + fTickleThread->wait(); + KPILOT_DELETE(fTickleThread); + } +} + +unsigned int KPilotLink::installFiles(const QStringList & l, const bool deleteFiles) +{ + FUNCTIONSETUP; + + QStringList::ConstIterator i,e; + unsigned int k = 0; + unsigned int n = 0; + unsigned int total = l.count(); + + for (i = l.begin(), e = l.end(); i != e; ++i) + { + emit logProgress(QString::null, + (int) ((100.0 / total) * (float) n)); + + if (installFile(*i, deleteFiles)) + k++; + n++; + } + emit logProgress(QString::null, 100); + + return k; +} + +void KPilotLink::addSyncLogEntry(const QString & entry, bool log) +{ + FUNCTIONSETUP; + if (entry.isEmpty()) return; + + addSyncLogEntryImpl(entry); + if (log) + { + emit logMessage(entry); + } +} + + +/* virtual */ int KPilotLink::openConduit() +{ + return 0; +} + +/* virtual */ int KPilotLink::pilotSocket() const +{ + return -1; +} + +/* virtual */ PilotDatabase *KPilotLink::database( const DBInfo *info ) +{ + FUNCTIONSETUP; + return database( Pilot::fromPilot( info->name ) ); +} + diff --git a/kpilot/lib/kpilotlink.h b/kpilot/lib/kpilotlink.h new file mode 100644 index 000000000..5c3865c3e --- /dev/null +++ b/kpilot/lib/kpilotlink.h @@ -0,0 +1,501 @@ +#ifndef _KPILOT_KPILOTLINK_H +#define _KPILOT_KPILOTLINK_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <pi-dlp.h> + +#include <qobject.h> +#include <qvaluelist.h> + +/** @file +* Encapsulates all the communication with the handheld. Also +* does daemon-like polling of the handheld. Interesting status +* changes are signalled. +*/ + +class QThread; +class KPilotUser; +class KPilotSysInfo; +class KPilotCard; +class PilotDatabase; + + + +/** +* KPilotLink handles some aspects of +* communication with a Handheld. A KPilotLink object represents a +* connection to a device (which may be active or inactive -- the latter in +* cases where the link is @e waiting for a device to show up). The object +* handles waiting, protocol initialization and some general +* tasks such as getting system information or user data. +* +* The actual communication with the handheld should use the +* PilotDatabase methods or use pilot-link dlp_* functions directly +* on the file descriptor returned by handle(). +* +* Implementations of this abstract class are KPilotDeviceLink +* (for real physical devices) and KPilotLocalLink (for devices +* represented by an on-disk directory). +* +* +* @section General +* +* A KPilotLink object (or one of its subclasses) represents a single +* (potential) link to a handheld device. The handheld device may be +* a real physical one (subclass KPilotDeviceLink) or a virtual one +* (subclass KPilotLocalLink). Every KPilotLink is associated with exactly +* one identifier for @em what device it is attached to. Physical devices +* have physical locations as interpreted by libpisock -- /dev/ttyUSB0 for +* instance, or net:any -- while virtual devices are associated with a location +* in the filesystem. +* +* A particular KPilotLink object may be connected -- communicating with +* a device -- or not. For physical devices, that means that the device is +* attached to the system (for USB-connected devices, think of it as a +* metaphor in the case of net:any) and that the HotSync button has been +* pressed. Virtual devices are immediately connected on creation, since there +* is no sensible "not connected" state. A connected KPilotLink has access to the +* data on the handheld and can give that data to the rest of the application. +* +* The data access API is divided into roughly three parts, with tickle handling +* being a special fourth part (see section below). These are: +* +* - Message logging +* - System information access +* - Database access +* +* @section Lifecycle +* +* The life-cycle of a KPilotLink object is as follows: +* +* # Object is created (one of the concrete subclasses, anyway) +* # Object gets a location assigned through reset(const QString &) +* # Object is connected to the handheld device (somehow, depends on subclass) +* # Object emits signal deviceReady() +* +* After this, the application is free to use the API to access the information from +* the handheld. When the device connection is no longer needed, call either +* endOfSync() or finishSync() to wrap up the communications. The object remains +* alive and may be re-used by calling reset() to use the same location or +* reset(const QString &) to give it a new location. +* +* @section Tickle handling. +* +* During a HotSync, the Pilot expects to be kept awake by (nearly) +* continuous communication with the PC. The Pilot doesn't like +* long periods of inactivity, since they drain the batteries while +* the communications hardware is kept powered up. If the period of +* inactivity is too long, the Pilot times out, shuts down the +* communication, and the HotSync is broken. + +* Sometimes, however, periods of inactivity cannot be avoided -- +* for instance, if you _have_ to ask the user something during a +* sync, or if you are fetching a large amount of data from a slow +* source (libkabc can do that, if your addressbook is on an LDAP +* server). During these periods of inactivity (as far as the Pilot +* can tell), you can "tickle" the Pilot to keep it awake. This +* prevents the communications from being shut down. It's not +* a good idea to do this all the time -- battery life and possible +* corruption of the dlp_ communications streams. Hence, you should +* start and stop tickling the Pilot around any computation which: +* - may take a long time +* - does not in itself @em ever communicate directly with the Pilot +* +* +* +* You can call slot tickle() whenever you like just to do a +* dlp_tickle() call on the Pilot. It will return true if the +* tickle was successful, false otherwise (this can be used to +* detect if the communication with the Pilot has shut down for +* some reason). +* +* The protected methods startTickle() and stopTickle() are intended +* to be called only from SyncActions -- I can't think of any other +* legitimate use, since everything being done during a HotSync is +* done via subclasses of SyncActions anyway, and SyncAction provides +* access to these methods though its own start- and stopTickle(). +* +* Call startTickle with a timeout in seconds, or 0 for no timeout. +* This timeout is _unrelated_ to the timeout in the Pilot's +* communications. Instead, it indicates how long to continue +* tickling the Pilot before emitting the timeout() signal. This +* can be useful for placing an upper bound on the amount of +* time to wait for, say, user interaction -- you don't want an +* inattentive user to drain the batteries during a sync because +* he doesn't click on "Yes" for some question. If you pass a +* timeout of 0, the Pilot will continue to be tickled until you +* call stopTickle(). +* +* Call stopTickle() to stop tickling the Pilot and continue with +* normal operation. You @em must call stopTickle() before calling +* anything else that might communicate with the Pilot, to avoid +* corrupting the dlp_ communications stream. (TODO: Mutex the heck +* out of this to avoid this problem). Note that stopTickle() may +* hang up the caller for a small amount of time (up to 200ms) +* before returning. +* +* event() and TickleTimeoutEvent are part of the implementation +* of tickling, and are only accidentally visible. +* +* Signal timeout() is emitted if startTickle() has been called +* with a non-zero timeout and that timeout has elapsed. The +* tickler is stopped before timeout is emitted. +*/ +class KDE_EXPORT KPilotLink : public QObject +{ +Q_OBJECT +friend class SyncAction; +public: + /** A list of DBInfo structures. */ + typedef QValueList<struct DBInfo> DBInfoList; + + /** Constructor. Use reset() to start looking for a device. */ + KPilotLink( QObject *parent = 0, const char *name = 0 ); + + /** Destructor. This rudely interrupts any communication in progress. + * It is best to call endOfSync() or finishSync() before destroying + * the device. + */ + virtual ~KPilotLink(); + + + /** Provides a human-readable status string. */ + virtual QString statusString() const = 0; + + /** + * True if HotSync has been started but not finished yet + * (ie. the physical Pilot is waiting for sync commands) + */ + virtual bool isConnected() const = 0; + + + /** + * Information on what kind of device we're dealing with. + * A link is associated with a path -- either the node in + * /dev that the physical device is attached to, or an + * IP address, or a filesystem path for local links. + * Whichever is being used, this function returns its + * name in a human-readable form. + */ + QString pilotPath() const + { + return fPilotPath; + } + + /** + * Return the device link to the Init state and try connecting + * to the given device path (if it's non-empty). What the + * path means depends on the kind of link we're instantiating. + * + * @see reset() + * @see pilotPath() + */ + virtual void reset(const QString &pilotPath) = 0; + + /** Allows our class to receive custom events that our threads + * will be giving to us, including tickle timeouts and + * device communication events. + */ + virtual bool event(QEvent *e); + + /** + * Install the list of files (full paths!) named by @p l + * onto the handheld (or whatever this link represents). + * If @p deleteFiles is true, the source files are removed. + * + * @return the number of files successfully installed. + */ + unsigned int installFiles(const QStringList &l, const bool deleteFiles); + + /** + * Write a log entry to the handheld. If @p log is true, + * then the signal logMessage() is also emitted. This + * function is supposed to @em only write to the handheld's + * log (with a physical device, that is what appears on + * screen at the end of a sync). + */ + void addSyncLogEntry(const QString &entry,bool log=true); + + /** + * Find a database with the given @p name (and optionally, + * type @p type and creator ID (from pi_mktag) @p creator, + * on searching from index @p index on the handheld. + * Fills in the DBInfo structure @p info if found. + * + * @return >=0 on success. See the documentation for each + * subclass for particular meanings. + * @return < 0 on error. + */ + virtual int findDatabase(const char *name, + struct DBInfo *info, + int index=0, + unsigned long type=0, + unsigned long creator=0) = 0; + + /** + * Retrieve the database indicated by DBInfo @p *db into the + * local file @p path. This copies all the data, and you can + * create a PilotLocalDatabase from the resulting @p path . + * + * @return @c true on success + */ + virtual bool retrieveDatabase(const QString &path, struct DBInfo *db) = 0; + + /** + * Fill the DBInfo structure @p db with information about + * the next database (in some ordering) counting from + * @p index. + * + * @return < 0 on error + */ + virtual int getNextDatabase(int index,struct DBInfo *db) = 0; + + /** + * Returns a list of DBInfo structures describing all the + * databases available on the link (ie. device) with the + * given card number @p cardno and flags @p flags. No known + * handheld uses a cardno other than 0; use flags to + * indicate what kind of databases to fetch -- @c dlpDBListRAM + * or @c dlpDBListROM. + * + * @return list of DBInfo objects, one for each database + * @note ownership of the DBInfo objects is passed to the + * caller, who must delete the objects. + */ + virtual DBInfoList getDBList(int cardno=0, int flags=dlpDBListRAM) = 0; + + /** + * Return a database object for manipulating the database with + * name @p name on the link. This database may be local or + * remote, depending on the kind of link in use. + * + * @return pointer to database object, or 0 on error. + * @note ownership of the database object is given to the caller, + * who must delete the object in time. + */ + virtual PilotDatabase *database( const QString &name ) = 0; + + /** + * Return a database object for manipulating the database with + * the name stored in the DBInfo structure @p info . The default + * version goes through method database( const QString & ), above. + * + * @return pointer to database object, or 0 on error. + * @note ownership of the database object is given to the caller. + */ + virtual PilotDatabase *database( const DBInfo *info ); + + /** + * Retrieve the user information from the device. Ownership + * is kept by the link, and at the end of a sync the user + * information is synced back to the link -- so it may be + * modified, but don't make local copies of it. + * + * @note Do not call this before the sync begins! + */ + KPilotUser &getPilotUser() + { + return *fPilotUser; + } + + /** + * System information about the handheld. Ownership is kept + * by the link. For non-device links, something fake is + * returned. + * + * @note Do not call this before the sync begins! + */ + const KPilotSysInfo &getSysInfo() + { + return *fPilotSysInfo; + } + + /** + * Retrieve information about the data card @p card; + * I don't think that any pilot supports card numbers + * other than 0. Non-device links return something fake. + * + * This function may return NULL (non-device links or + * on error). + * + * @note Ownership of the KPilotCard object is given + * to the caller, who must delete it. + */ + virtual const KPilotCard *getCardInfo(int card=0) = 0; + + /** + * When ending the sync, you can do so gracefully, updating the + * last-sync time to indicate a successful sync and setting the + * user name on the device, or you can skip that (for unsuccessful + * syncs, generally). + */ + enum EndOfSyncFlags { + NoUpdate, ///< Do not update the user info + UpdateUserInfo ///< Update user info and last successful sync date + } ; + + /** + * Custom events we can be handling... + */ + enum CustomEvents { + EventTickleTimeout = 1066 + }; + + /** + * End the sync in a gracuful manner. If @p f is UpdateUserInfo, + * the sync was successful and the user info and last successful sync + * timestamp are updated. + */ + virtual void endSync( EndOfSyncFlags f ) = 0; + +signals: + /** + * A timeout associated with tickling has occurred. Each + * time startTickle() is called, you can state how long + * tickling should last (at most) before timing out. + * + * You can only get a timeout when the Qt event loop is + * running, which somewhat limits the usefulness of timeouts. + */ + void timeout(); + + /** Signal that a message has been written to the sync log. */ + void logMessage(const QString &); + + /** Signal that an error has occurred, for logging. */ + void logError(const QString &); + + /** + * Signal that progress has been made, for logging purposes. + * @p p is the percentage completed (0 <= s <= 100). + * The string @p s is logged as well, if non-Null. + */ + void logProgress(const QString &s, int p); + + /** + * Emitted once the user information has been read and + * the HotSync is really ready to go. + */ + void deviceReady( KPilotLink * ); + + +public slots: + /** + * Release all resources, including the master pilot socket, + * timers, etc. + */ + virtual void close() = 0; + + /** + * Assuming things have been set up at least once already by + * a call to reset() with parameters, use this slot to re-start + * with the same settings. + */ + virtual void reset() = 0; + + /** Tickle the underlying device exactly once. */ + virtual bool tickle() = 0; + +protected: + /** + * Path of the device special file that will be used. + * Usually /dev/pilot, /dev/ttySx, or /dev/usb/x. May be + * a filesystem path for local links. + */ + QString fPilotPath; + + /** + * Start tickling the Handheld (every few seconds). This + * lasts until @p timeout seconds have passed (or forever + * if @p timeout is zero). + * + * @note Do not call startTickle() twice with no intervening + * stopTickle(). + */ + void startTickle(unsigned int timeout=0); + + /** + * Stop tickling the Handheld. This may block for some + * time (less than a second) to allow the tickle thread + * to finish. + */ + void stopTickle(); + + /** + * Install a single file onto the device link. Full pathname + * @p f is used; in addition, if @p deleteFile is true remove + * the source file. Returns @c true if the install succeeded. + */ + virtual bool installFile( const QString &f, const bool deleteFile ) = 0; + + /** + * Notify the Pilot user that a conduit is running now. + * On real devices, this prints out (on screen) which database + * is now opened; useful for progress reporting. + * + * @return -1 on error + * @note the default implementation returns 0 + */ + virtual int openConduit(); + + /** + * Returns a file handle for raw operations. Not recommended. + * On links with no physical device backing, returns -1. + * + * @note the default implementation returns -1 + */ + virtual int pilotSocket() const; + + /** + * Actually write an entry to the device link. The message + * @p s must be guaranteed non-empty. + */ + virtual void addSyncLogEntryImpl( const QString &s ) = 0; + + /** + * User information structure. Should be filled in when a sync + * starts, so that conduits can use the information. + */ + KPilotUser *fPilotUser; + + /** + * System information about the device. Filled in when the + * sync starts. Non-device links need to fake something. + */ + KPilotSysInfo *fPilotSysInfo; + + +private: + bool fTickleDone; + QThread *fTickleThread; + +} ; + +#endif diff --git a/kpilot/lib/kpilotlocallink.cc b/kpilot/lib/kpilotlocallink.cc new file mode 100644 index 000000000..c3af1d342 --- /dev/null +++ b/kpilot/lib/kpilotlocallink.cc @@ -0,0 +1,368 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006-2007 Adriaan de Groot <groot@kde.org> +** Copyright (C) 2007 Jason 'vanRijn' Kasper <vR@movingparts.net> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + + + +#include <sys/stat.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <iostream> + +#include <pi-source.h> +#include <pi-socket.h> +#include <pi-dlp.h> +#include <pi-file.h> +#include <pi-buffer.h> + +#include <qdir.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qthread.h> + +#include <kconfig.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kurl.h> +#include <kio/netaccess.h> + +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" + +#include "kpilotlink.h" +#include "kpilotlocallink.moc" + + +typedef QPair<QString, struct DBInfo> DatabaseDescriptor; +typedef QValueList<DatabaseDescriptor> DatabaseDescriptorList; + +class KPilotLocalLink::Private +{ +public: + DatabaseDescriptorList fDBs; +} ; + +unsigned int KPilotLocalLink::findAvailableDatabases( KPilotLocalLink::Private &info, const QString &path ) +{ + FUNCTIONSETUP; + + info.fDBs.clear(); + + QDir d(path); + if (!d.exists()) + { + // Perhaps return an error? + return 0; + } + + // Use this to fake indexes in the list of DBInfo structs + unsigned int counter = 0; + + QStringList dbs = d.entryList( CSL1("*.pdb"), QDir::Files | QDir::NoSymLinks | QDir::Readable ); + for ( QStringList::ConstIterator i = dbs.begin(); i != dbs.end() ; ++i) + { + struct DBInfo dbi; + + // Remove the trailing 4 characters + QString dbname = (*i); + dbname.remove(dbname.length()-4,4); + + QString dbnamecheck = (*i).left((*i).findRev(CSL1(".pdb"))); + Q_ASSERT(dbname == dbnamecheck); + + if (PilotLocalDatabase::infoFromFile( path + CSL1("/") + (*i), &dbi)) + { + DEBUGKPILOT << fname << ": Loaded " + << dbname << endl; + dbi.index = counter; + info.fDBs.append( DatabaseDescriptor(dbname,dbi) ); + ++counter; + } + } + + DEBUGKPILOT << fname << ": Total " << info.fDBs.count() + << " databases." << endl; + return info.fDBs.count(); +} + + +KPilotLocalLink::KPilotLocalLink( QObject *parent, const char *name ) : + KPilotLink(parent,name), + fReady(false), + d( new Private ) +{ + FUNCTIONSETUP; +} + +KPilotLocalLink::~KPilotLocalLink() +{ + FUNCTIONSETUP; + KPILOT_DELETE(d); +} + +/* virtual */ QString KPilotLocalLink::statusString() const +{ + return fReady ? CSL1("Ready") : CSL1("Waiting") ; +} + +/* virtual */ bool KPilotLocalLink::isConnected() const +{ + return fReady; +} + +/* virtual */ void KPilotLocalLink::reset( const QString &p ) +{ + FUNCTIONSETUP; + fPath = p; + reset(); +} + +/* virtual */ void KPilotLocalLink::reset() +{ + FUNCTIONSETUP; + QFileInfo info( fPath ); + fReady = !fPath.isEmpty() && info.exists() && info.isDir() ; + if (fReady) + { + findAvailableDatabases(*d, fPath); + QTimer::singleShot(500,this,SLOT(ready())); + } + else + { + WARNINGKPILOT << "The local link path <" + << fPath + << "> does not exist or is not a directory. No sync can be done." + << endl; + } +} + +/* virtual */ void KPilotLocalLink::close() +{ + fReady = false; +} + +/* virtual */ bool KPilotLocalLink::tickle() +{ + return true; +} + +/* virtual */ const KPilotCard *KPilotLocalLink::getCardInfo(int) +{ + return 0; +} + +/* virtual */ void KPilotLocalLink::endSync( EndOfSyncFlags f ) +{ + Q_UNUSED(f); + fReady = false; +} + +/* virtual */ int KPilotLocalLink::openConduit() +{ + FUNCTIONSETUP; + return 0; +} + + +/* virtual */ int KPilotLocalLink::getNextDatabase( int index, struct DBInfo *info ) +{ + FUNCTIONSETUP; + + if ( (index<0) || (index>=(int)d->fDBs.count()) ) + { + WARNINGKPILOT << "Index out of range." << endl; + return -1; + } + + DatabaseDescriptor dd = d->fDBs[index]; + + DEBUGKPILOT << fname << ": Getting database " << dd.first << endl; + + if (info) + { + *info = dd.second; + } + + return index+1; +} + +/* virtual */ int KPilotLocalLink::findDatabase(const char *name, struct DBInfo*info, + int index, unsigned long type, unsigned long creator) +{ + FUNCTIONSETUP; + + if ( (index<0) || (index>=(int)d->fDBs.count()) ) + { + WARNINGKPILOT << "Index out of range." << endl; + return -1; + } + + if (!name) + { + WARNINGKPILOT << "NULL name." << endl; + return -1; + } + + QString desiredName = Pilot::fromPilot(name); + DEBUGKPILOT << fname << ": Looking for DB " << desiredName << endl; + for ( DatabaseDescriptorList::ConstIterator i = d->fDBs.at(index); + i != d->fDBs.end(); ++i) + { + const DatabaseDescriptor &dd = *i; + if (dd.first == desiredName) + { + if ( (!type || (type == dd.second.type)) && + (!creator || (creator == dd.second.creator)) ) + { + if (info) + { + *info = dd.second; + } + return index; + } + } + + ++index; + } + + return -1; +} + +/* virtual */ void KPilotLocalLink::addSyncLogEntryImpl(QString const &s) +{ + FUNCTIONSETUP; + DEBUGKPILOT << fname << ": " << s << endl ; +} + +/* virtual */ bool KPilotLocalLink::installFile(QString const &path, bool deletefile) +{ + FUNCTIONSETUP; + + QFileInfo srcInfo(path); + QString canonicalSrcPath = srcInfo.dir().canonicalPath() + CSL1("/") + srcInfo.fileName() ; + QString canonicalDstPath = fPath + CSL1("/") + srcInfo.fileName(); + + if (canonicalSrcPath == canonicalDstPath) + { + // That's a cheap copy operation + return true; + } + + KURL src = KURL::fromPathOrURL( canonicalSrcPath ); + KURL dst = KURL::fromPathOrURL( canonicalDstPath ); + + KIO::NetAccess::file_copy(src,dst,-1,true); + + if (deletefile) + { + KIO::NetAccess::del(src, 0L); + } + + return true; +} + +/* virtual */ bool KPilotLocalLink::retrieveDatabase( const QString &path, struct DBInfo *db ) +{ + FUNCTIONSETUP; + + QString dbname = Pilot::fromPilot(db->name) + CSL1(".pdb") ; + QString sourcefile = fPath + CSL1("/") + dbname ; + QString destfile = path ; + + DEBUGKPILOT << fname << ": src=" << sourcefile << endl; + DEBUGKPILOT << fname << ": dst=" << destfile << endl; + + QFile in( sourcefile ); + if ( !in.exists() ) + { + WARNINGKPILOT << "Source file " << sourcefile << " doesn't exist." << endl; + return false; + } + if ( !in.open( IO_ReadOnly | IO_Raw ) ) + { + WARNINGKPILOT << "Can't read source file " << sourcefile << endl; + return false; + } + + QFile out( destfile ); + if ( !out.open( IO_WriteOnly | IO_Truncate | IO_Raw ) ) + { + WARNINGKPILOT << "Can't write destination file " << destfile << endl; + return false; + } + + const Q_ULONG BUF_SIZ = 8192 ; + char buf[BUF_SIZ]; + Q_LONG r; + + while ( (r=in.readBlock(buf,BUF_SIZ))>0 ) + { + out.writeBlock(buf,r); + } + out.flush(); + in.close(); + + return out.exists(); +} + +KPilotLink::DBInfoList KPilotLocalLink::getDBList( int, int ) +{ + FUNCTIONSETUP; + DBInfoList l; + for ( DatabaseDescriptorList::ConstIterator i=d->fDBs.begin(); + i != d->fDBs.end(); ++i) + { + l.append( (*i).second ); + } + return l; +} + + +/* virtual */ PilotDatabase *KPilotLocalLink::database( const QString &name ) +{ + FUNCTIONSETUP; + return new PilotLocalDatabase( fPath, name ); +} + + + +/* slot */ void KPilotLocalLink::ready() +{ + if (fReady) + { + emit deviceReady(this); + } +} + diff --git a/kpilot/lib/kpilotlocallink.h b/kpilot/lib/kpilotlocallink.h new file mode 100644 index 000000000..f18556b3c --- /dev/null +++ b/kpilot/lib/kpilotlocallink.h @@ -0,0 +1,95 @@ +#ifndef _KPILOT_KPILOTLOCALLINK_H +#define _KPILOT_KPILOTLOCALLINK_H +/* +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "kpilotlink.h" + +/** @file +* Definition of the local link class; implemented in kpilotlink.cc . +*/ + + +/** +* Implementation of the device link for file-system backed (ie. local, fake) +* devices. Uses a directory specified in the reset() call to serve databases. +*/ +class KDE_EXPORT KPilotLocalLink : public KPilotLink +{ +Q_OBJECT +public: + KPilotLocalLink( QObject *parent=0L, const char *name=0L ); + virtual ~KPilotLocalLink(); + + virtual QString statusString() const; + virtual bool isConnected() const; + virtual void reset( const QString & ); + virtual void close(); + virtual void reset(); + virtual bool tickle(); + virtual const KPilotCard *getCardInfo(int card); + virtual void endSync( EndOfSyncFlags f ); + virtual int openConduit(); + virtual int getNextDatabase(int index,struct DBInfo *); + virtual int findDatabase(const char *name, struct DBInfo*, + int index=0, unsigned long type=0, unsigned long creator=0); + virtual bool retrieveDatabase(const QString &path, struct DBInfo *db); + virtual DBInfoList getDBList(int cardno=0, int flags=dlpDBListRAM); + virtual PilotDatabase *database( const QString &name ); + +public slots: + void ready(); + +protected: + virtual bool installFile(const QString &, const bool deleteFile); + virtual void addSyncLogEntryImpl( const QString &s ); + virtual int pilotSocket() const + { + return -1; + } + +protected: + bool fReady; + QString fPath; + + class Private; + Private *d; + + /** + * Pre-process the directory @p path to find out which databases + * live there. + * + * @return Number of database in @p path. + */ + unsigned int findAvailableDatabases( Private &, const QString &path ); +} ; + + +#endif + diff --git a/kpilot/lib/options.cc b/kpilot/lib/options.cc new file mode 100644 index 000000000..0eb1babf7 --- /dev/null +++ b/kpilot/lib/options.cc @@ -0,0 +1,157 @@ +/* KPilot +** +** Copyright (C) 2000-2001 by Adriaan de Groot +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This is a file of odds and ends, with debugging functions and stuff. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "options.h" + + +#include <iostream> + +#include <qsize.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#ifdef DEBUG +int debug_level = 1; +#else +int debug_level = 0; +#endif + +// The daemon also has a debug level; debug_spaces is 60 spaces, +// to align FUNCTIONSETUP output. The one byte extra is for the NUL. +// +// +static const char debug_spaces[61] = + " " + " " + " "; + + +QString rtExpand(const QString &s, Qt::TextFormat richText) +{ + if (richText == Qt::RichText) + { + QString t(s); + return t.replace(CSL1("\n"), CSL1("<br/>\n")); + } + else + { + return s; + } + +} + +QDateTime readTm(const struct tm &t) +{ + QDateTime dt; + dt.setDate(QDate(1900 + t.tm_year, t.tm_mon + 1, t.tm_mday)); + dt.setTime(QTime(t.tm_hour, t.tm_min, t.tm_sec)); + return dt; +} + + + +struct tm writeTm(const QDateTime &dt) +{ + struct tm t; + + t.tm_wday = 0; // unimplemented + t.tm_yday = 0; // unimplemented + t.tm_isdst = 0; // unimplemented +#ifdef HAVE_STRUCT_TM_TM_ZONE + t.tm_zone = 0; // unimplemented +#endif + + t.tm_year = dt.date().year() - 1900; + t.tm_mon = dt.date().month() - 1; + t.tm_mday = dt.date().day(); + t.tm_hour = dt.time().hour(); + t.tm_min = dt.time().minute(); + t.tm_sec = dt.time().second(); + + return t; +} + + + +struct tm writeTm(const QDate &d) +{ + QDateTime dt(d); + return writeTm(dt); +} + +KPilotDepthCount::KPilotDepthCount(int, int level, const char *s) : + fDepth(depth), + fLevel(level), + fName(s) +{ + DEBUGKPILOT << "! DEPRECATED Depth call.\n! " + << kdBacktrace(4) << endl; + + if (debug_level>=fLevel) + { + DEBUGKPILOT << indent() << ">" << name() << endl; + } + depth++; +} + +KPilotDepthCount::KPilotDepthCount(int level, const char *s) : + fDepth(depth), + fLevel(level), + fName(s) +{ + if (debug_level>=fLevel) + { + DEBUGKPILOT << indent() << ">" << name() << endl; + } + depth++; +} + +KPilotDepthCount::~KPilotDepthCount() +{ + depth--; + std::cerr.clear(std::ios_base::goodbit); +} + +const char *KPilotDepthCount::indent() const +{ + if (fDepth < 30) + { + return debug_spaces + 60-fDepth*2; + } + else + { + return debug_spaces; + } +} + +int KPilotDepthCount::depth = 0; + diff --git a/kpilot/lib/options.h b/kpilot/lib/options.h new file mode 100644 index 000000000..6e036e82d --- /dev/null +++ b/kpilot/lib/options.h @@ -0,0 +1,199 @@ +#ifndef _KPILOT_OPTIONS_H +#define _KPILOT_OPTIONS_H +/* options.h KPilot +** +** Copyright (C) 1998-2001,2002,2003 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This file defines some global constants and macros for KPilot. +** In particular, KDE2 is defined when KDE2 seems to be the environment +** (is there a better way to do this?). Use of KDE2 to #ifdef sections +** of code is deprecated though. +** +** Many debug functions are defined as well. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "config.h" + +#include <qglobal.h> +#include <qnamespace.h> +#include <qstring.h> + +#if (QT_VERSION < 0x030300) +#error "This is KPilot for KDE3.5 and won't compile with Qt < 3.3.0" +#endif + +#include <kdebug.h> +#include <kdeversion.h> +#include <klocale.h> + +#if !(KDE_IS_VERSION(3,4,0)) +#error "This is KPilot for (really) KDE 3.5 and won't compile with KDE < 3.4.0" +#endif + +#if !(KDE_IS_VERSION(3,5,0)) +#warning "This is KPilot for KDE 3.5 and might not compile with KDE < 3.5.0" +#endif + +#include "pilotLinkVersion.h" + +#include <iostream> + +using namespace std; +inline std::ostream& operator <<(std::ostream &o, const QString &s) + { if (s.isEmpty()) return o<<"<empty>"; else return o<<s.latin1(); } +inline std::ostream& operator <<(std::ostream &o, const QCString &s) + { if (s.isEmpty()) return o<<"<empty>"; else return o << *s; } + + +#ifndef NDEBUG +#define DEBUG (1) +#endif + +extern KDE_EXPORT int debug_level; + +class KDE_EXPORT KPilotDepthCount +{ +public: + KPilotDepthCount(int, int level, const char *s); + KPilotDepthCount(int level, const char *s); + ~KPilotDepthCount(); + const char *indent() const; + inline const char *name() const { return fName; } ; + inline int level() const { return fLevel; } ; + +protected: + static int depth; + int fDepth; + int fLevel; + const char *fName; +} ; + + +#ifdef DEBUG +#ifdef __GNUC__ +#define KPILOT_FNAMEDEF(l) KPilotDepthCount fname(l,__FUNCTION__) +#else +#define KPILOT_FNAMEDEF(l) KPilotDepthCount fname(l,__FILE__ ":" "__LINE__") +#endif + +#define FUNCTIONSETUP KPILOT_FNAMEDEF(1) +#define FUNCTIONSETUPL(l) KPILOT_FNAMEDEF(l) + +// stderr / iostream-based debugging. +// +// +#define DEBUGKPILOT std::cerr +#define WARNINGKPILOT std::cerr.clear(std::ios_base::goodbit),\ + std::cerr << "! " << k_funcinfo << std::endl << "! " + + + + +inline std::ostream& operator <<(std::ostream &o, const KPilotDepthCount &d) +{ + if (debug_level >= d.level()) + { + o.clear(std::ios_base::goodbit); + return o << d.indent() << ' ' << d.name(); + } + else + { + o.setstate(std::ios_base::badbit | std::ios_base::failbit); + return o; + } +} + +#else + +// no debugging at all +// +#define DEBUGSTREAM kndbgstream +#define DEBUGKPILOT kndDebug() +#define WARNINGKPILOT kndDebug() + +// With debugging turned off, FUNCTIONSETUP doesn't do anything. +// +// +#define FUNCTIONSETUP const int fname = 0; Q_UNUSED(fname); +#define FUNCTIONSETUPL(a) const int fname = a; Q_UNUSED(fname); +#endif + +#define KPILOT_VERSION "4.9.4-3510 (elsewhere)" + + +// Function to expand newlines in rich text to <br>\n +QString rtExpand(const QString &s, Qt::TextFormat richText); + + + +/** + * Convert a struct tm from the pilot-link package to a QDateTime + */ +KDE_EXPORT QDateTime readTm(const struct tm &t); +/** + * Convert a QDateTime to a struct tm for use with the pilot-link package + */ +KDE_EXPORT struct tm writeTm(const QDateTime &dt); +KDE_EXPORT struct tm writeTm(const QDate &dt); + + +// Some layout macros +// +// SPACING is a generic distance between visual elements; +// 10 seems reasonably good even at high resolutions. +// +// +#define SPACING (10) + +// Semi-Standard safe-free expression. Argument a may be evaluated more +// than once though, so be careful. +// +// +#define KPILOT_FREE(a) { if (a) { ::free(a); a=0L; } } +#define KPILOT_DELETE(a) { if (a) { delete a; a=0L; } } + + +// This marks strings that need to be i18n()ed in future, +// but cannot be done now due to message freeze. The _P +// variant is to handle plurals and is wrong, but unavoidable. +// +// +#define TODO_I18N(a) QString::fromLatin1(a) +#define TODO_I18N_P(a,b,c) ((c>1) ? a : b) + +// Handle some cases for QT_NO_CAST_ASCII and NO_ASCII_CAST. +// Where possible in the source, known constant strings in +// latin1 encoding are marked with CSL1(), to avoid gobs +// of latin1() or fromlatin1() calls which might obscure +// those places where the code really is translating +// user data from latin1. +// +// The extra "" in CSL1 is to enforce that it's only called +// with constant strings. +// +// +#define CSL1(a) QString::fromLatin1(a "") + +#endif diff --git a/kpilot/lib/pilot.cc b/kpilot/lib/pilot.cc new file mode 100644 index 000000000..2585445c1 --- /dev/null +++ b/kpilot/lib/pilot.cc @@ -0,0 +1,264 @@ +/* pilot.cc KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2003-2006 Adriaan de Groot <groot@kde.org> +** +** These are the base class structures that reside on the +** handheld device -- databases and their parts. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <qtextcodec.h> +#include <qmutex.h> +#include <kcharsets.h> +#include <kglobal.h> + +#include "pilot.h" +#include "pilotDatabase.h" +#include "pilotAppInfo.h" +#include "pilotRecord.h" + + +namespace Pilot +{ +static QTextCodec *codec = 0L; +static QMutex* mutex = 0L; + + +QString fromPilot( const char *c, int len ) +{ + mutex->lock(); + QString str = codec->toUnicode(c,len); + mutex->unlock(); + return str; +} + +QString fromPilot( const char *c ) +{ + mutex->lock(); + QString str = codec->toUnicode(c); + mutex->unlock(); + return str; +} + +QCString toPilot( const QString &s ) +{ + mutex->lock(); + QCString str = codec->fromUnicode(s); + mutex->unlock(); + return str; +} + +int toPilot( const QString &s, char *buf, int len) +{ + mutex->lock(); + // See toPilot() below. + memset( buf, 0, len ); + int used = len; + QCString cbuf = codec->fromUnicode(s,used); + if (used > len) + { + used=len; + } + memcpy( buf, cbuf.data(), used ); + mutex->unlock(); + return used; +} + +int toPilot( const QString &s, unsigned char *buf, int len) +{ + mutex->lock(); + // Clear the buffer + memset( buf, 0, len ); + + // Convert to 8-bit encoding + int used = len; + QCString cbuf = codec->fromUnicode(s,used); + + // Will it fit in the buffer? + if (used > len) + { + // Ought to be impossible, anyway, since 8-bit encodings + // are shorter than the UTF-8 encodings (1 byte per character + // vs. 1-or-more byte per character). + used=len; + } + + // Fill the buffer with encoded data. + memcpy( buf, cbuf.data(), used ); + mutex->unlock(); + return used; +} + +bool setupPilotCodec(const QString &s) +{ + FUNCTIONSETUP; + mutex = new QMutex(); + mutex->lock(); + QString encoding(KGlobal::charsets()->encodingForName(s)); + + DEBUGKPILOT << fname << ": Using codec name " << s << endl; + DEBUGKPILOT << fname << ": Creating codec " << encoding << endl; + + // if the desired codec can't be found, latin1 will be returned anyway, no need to do this manually + codec = KGlobal::charsets()->codecForName(encoding); + + if (codec) + { + DEBUGKPILOT << fname << ": Got codec " << codec->name() << endl; + } + + mutex->unlock(); + return codec; +} + +QString codecName() +{ + return QString::fromLatin1(codec->name()); +} + +QString category(const struct CategoryAppInfo *info, unsigned int i) +{ + if (!info || (i>=CATEGORY_COUNT)) + { + return QString::null; + } + + mutex->lock(); + QString str = codec->toUnicode(info->name[i], + MIN(strlen(info->name[i]), CATEGORY_SIZE-1)); + mutex->unlock(); + return str; +} + + +int findCategory(const struct CategoryAppInfo *info, + const QString &selectedCategory, + bool unknownIsUnfiled) +{ + FUNCTIONSETUP; + + if (!info) + { + WARNINGKPILOT << "Bad CategoryAppInfo pointer" << endl; + return -1; + } + + int currentCatID = -1; + for (unsigned int i=0; i<CATEGORY_COUNT; i++) + { + if (!info->name[i][0]) continue; + if (selectedCategory == category(info, i)) + { + currentCatID = i; + break; + } + } + + if (-1 == currentCatID) + { + DEBUGKPILOT << fname << ": Category name " + << selectedCategory << " not found." << endl; + } + else + { + DEBUGKPILOT << fname << ": Matched category " << currentCatID << endl; + } + + if ((currentCatID == -1) && unknownIsUnfiled) + currentCatID = 0; + return currentCatID; +} + +int insertCategory(struct CategoryAppInfo *info, + const QString &label, + bool unknownIsUnfiled) +{ + FUNCTIONSETUP; + + if (!info) + { + WARNINGKPILOT << "Bad CategoryAppInfo pointer" << endl; + return -1; + } + + + int c = findCategory(info,label,unknownIsUnfiled); + if (c<0) + { + // This is the case when the category is not known + // and unknownIsUnfiled is false. + for (unsigned int i=0; i<CATEGORY_COUNT; i++) + { + if (!info->name[i][0]) + { + c = i; + break; + } + } + + if ((c>0) && (c < (int)CATEGORY_COUNT)) + { + // 0 is always unfiled, can't change that. + toPilot(label,info->name[c],CATEGORY_SIZE); + } + else + { + WARNINGKPILOT << "Category name " + << label + << " could not be added." << endl; + c = -1; + } + } + + return c; +} + +void dumpCategories(const struct CategoryAppInfo *info) +{ + FUNCTIONSETUP; + + if (!info) + { + WARNINGKPILOT << "Dumping bad pointer." << endl; + return; + } + + DEBUGKPILOT << fname << " lastUniqueId: " + << (int) info->lastUniqueID << endl; + for (unsigned int i = 0; i < CATEGORY_COUNT; i++) + { + if (!info->name[i][0]) continue; + DEBUGKPILOT << fname << ": " << i << " = " + << (int)(info->ID[i]) << " <" + << info->name[i] << ">" << endl; + } +} + + +} + + diff --git a/kpilot/lib/pilot.h b/kpilot/lib/pilot.h new file mode 100644 index 000000000..d4fec82b2 --- /dev/null +++ b/kpilot/lib/pilot.h @@ -0,0 +1,410 @@ +#ifndef _KPILOT_PILOT_H +#define _KPILOT_PILOT_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2003-2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <sys/types.h> + +#include <pi-appinfo.h> +#include <pi-buffer.h> +#include <pi-dlp.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +#include "pilotLinkVersion.h" + + +/** @file +* These are some base structures that reside on the +* handheld device -- strings and binary data. +*/ + +class PilotDatabase; // A database +class PilotRecord; // ... has records +class PilotCategoryInfo; // ... and category information + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) + +/** +* The Pilot namespace holds constants that are global for +* the handheld data structures. Also contains some global +* functions that deal with pilot-link structures as well +* as mapping user-visible strings from UTF8 (KDE side) to +* the encoding used on the handheld. +*/ +namespace Pilot +{ + /** Maximum size of an AppInfo block, taken roughly from the pilot-link source. */ + static const int MAX_APPINFO_SIZE=8192; + + /** Maximum number of categories the handheld has */ + static const unsigned int CATEGORY_COUNT=16; + + /** Maximum size of a category label */ + static const unsigned int CATEGORY_SIZE=16; + + /** Category number for unfiled records */ + static const int Unfiled = 0; + + /** Maximum size (in bytes) of a record's data */ + static const int MAX_RECORD_SIZE = 65535; + + typedef QValueList<recordid_t> RecordIDList; + + /** Static translation function that maps handheld native (8 bit, + * usually latin1 but sometimes someting else) encoded data to + * a Unicode string. Converts the @p len characters in @p c + * to a Unicode string. + */ + QString fromPilot( const char *c, int len ); + + /** Static translation function mapping a NUL-terminated + * string from the handheld's encoding to UTF-8. + * @param c the NUL-terminated string to decode + * @return QString (UTF-8) value of @p c + * @note NUL-terminated strings are rare on the handheld. + */ + QString fromPilot( const char *c ); + + /** Static translation function that maps a QString onto the + * native 8 bit encoding of the handheld. Writes the result into + * the buffer @p buf which has size @p len. Returns the length + * of the result. Zero-fills the buffer as needed. + */ + int toPilot( const QString &s, char *buf, int len); + int toPilot( const QString &s, unsigned char *buf, int len); + + /** Static translation function that maps a QString onto the + * native 8 bit encoding of the handheld. + * + * @param s String to encode + * @return Encoded string in a QCString + */ + QCString toPilot( const QString &s ); + + /** Create a codec for translating handheld native 8 bit to Unicode, + * using the given codec @p name -- this will often be latin1, but + * might be something else for, say, Russian-language Pilots. + * If @p name is empty, use latin1. + * + * @return @c true on success, @c false otherwise + */ + bool setupPilotCodec(const QString &name); + + /** Returns the name of the codec being used. */ + QString codecName(); + + /** For debugging, display category names for the given AppInfo + * structure. Called by dump(). You must pass a valid reference. + */ + void dumpCategories(const struct CategoryAppInfo *info); + + /** Check that a given category number is valid. This + * restricts the range of integers to [0..CATEGORY_COUNT-1] + * (i.e. [0..15]) which is what the handheld supports. + */ + inline bool validCategory(int c) + { + if (c<0) + { + return false; + } + return ((unsigned int)c<CATEGORY_COUNT); + } + + /** Returns the QString for the requested category @p i + * in the category structure @p info. Returns @c QString::null + * on error (bad pointer or bad category number). May also + * return @c QString::null if the category name is empty. + */ + inline QString categoryName(const struct CategoryAppInfo *info, unsigned int i) + { + if ( ( i < CATEGORY_COUNT ) && ( info->name[i][0] ) ) + { + /* + * Seems to be important that we try to pass the real length here + * to the codec. + */ + return fromPilot( info->name[i], MIN(strlen(info->name[i]),CATEGORY_SIZE) ); + } + else + { + return QString::null; + } + } + + /** Returns a list of all the category names available on the + * handheld. This list is neither ordered nor does it contain + * all sixteen categories -- empty category names on the + * handheld are skipped. + */ + inline QStringList categoryNames(const struct CategoryAppInfo *info) + { + QStringList l; + if (!info) + { + return l; + } + for (unsigned int i=0; i<CATEGORY_COUNT; ++i) + { + QString s = categoryName(info,i); + if (!s.isEmpty()) + { + l.append(s); + } + } + return l; + } + + /** Search for the given category @p name in the list + * of categories; returns the category number. If @p unknownIsUnfiled + * is true, then map unknown categories to Unfiled instead of returning + * an error number. + * + * @return >=0 is a specific category based on the text-to- + * category number mapping defined by the Pilot, + * where 0 is always the 'unfiled' category. + * @return -1 means unknown category selected when + * @p unknownIsUnfiled is false. + * @return 0 == Unfiled means unknown category selected when + * @p unknownIsUnfiled is true. + * + */ + int findCategory(const struct CategoryAppInfo *info, const QString &name, bool unknownIsUnfiled); + + /** Search for the given category @p name in the list + * of categories; returns the category number. If @p unknownIsUnfiled + * is @c true, then map unknown categories to Unfiled. + * If @p unknownIsUnfiled is @c false, insert a @em new + * category into the structure and return the category + * number of the new category. Return -1 if (and only if) + * @p unknownIsUnfiled is false and the category structure + * is already full. + * + * @return >=0 is a specific category based on the text-to- + * category number mapping defined by the Pilot, + * where 0 is always the 'unfiled' category. + * @return 0 Unknown category and @p unknownIsUnfiled is @c true + * @return -1 means unknown category selected when + * @p unknownIsUnfiled is false and categories + * are all full. + * + */ + int insertCategory(struct CategoryAppInfo *info, const QString &label, bool unknownIsUnfiled); + + /** The handheld also holds data about each database + * in a DBInfo structure; check if the database described + * by this structure is a resource database. + */ + static inline bool isResource(struct DBInfo *info) + { + return (info->flags & dlpDBFlagResource); + } + + +/** @section Binary blob handling +* +* For reading and writing binary blobs -- which has to happen to +* pack data into the format that the handheld needs -- it is important +* to remember that the handheld has only four data types (as far +* as I can tell: byte, short (a 2 byte integer), long (a 4 byte integer) +* and string (NUL terminated). The sizes of the types on the handheld +* do not necessarily correspond to the sizes of the same-named types +* on the desktop. This means that 'reading a long' from a binary +* blob must always be 4 bytes -- not sizeof(long). +* +* The following templates help out in manipulating the blobs. +* Instantiate them with the type @em name you need (char, short, long or +* char *) and you get a ::size enum specifying the number of bytes +* (where applicable) and ::append and ::read methods for appending +* a value of the given type to a pi_buffer_t or reading one from +* the buffer, respectively. +* +* The usage of ::read and ::append is straightforward: +* +* append(pi_buffer_t *b, TYPE_VALUE v) Appends the type value @p v to the +* buffer @p b , extending the buffer as needed. +* +* TYPE_VALUE read(pi_buffer_t *b, unsigned int &offset) Read a value from +* the buffer @p b at position @p offset and return it. The offset value +* is increased by the number of bytes read from the buffer. +* +* To write a binary blob, a sequence of ::append calls constructs the +* blob. To read the same blob, a sequence of ::read calls with the +* @em same type parameters is sufficient. +* +* The calls may vary a little: the exact interface differs depending +* on the needs of the type of data to be written to the blob. +*/ +template<typename t> struct dlp { } ; + +template<> struct dlp<char> +{ + enum { size = 1 }; + + static void append(pi_buffer_t *b, char v) + { + pi_buffer_append(b,&v,size); + } + + /** + * Returns next byte from buffer or 0 on error (0 is also a + * valid return value, though). + */ + static char read(const pi_buffer_t *b, unsigned int &offset) + { + if (offset+size > b->used) + { + return 0; + } + char c = b->data[offset]; + offset+=size; + return c; + } +} ; + +template<> struct dlp<short> +{ + enum { size = 2 }; + + static void append(pi_buffer_t *b, short v) + { + char buf[size]; + set_short(buf,v); + pi_buffer_append(b,buf,size); + } + + /** + * Returns the next short (2 byte) value from the buffer, or + * -1 on error (which is also a valid return value). + */ + static int read(const pi_buffer_t *b, unsigned int &offset) + { + if (offset+size > b->used) + { + return -1; + } + else + { + int r = get_short(b->data + offset); + offset+=size; + return r; + } + } + + /** + * Overload to read from a data buffer instead of a real pi_buffer; + * does no bounds checking. + */ + static int read(const unsigned char *b, unsigned int &offset) + { + int r = get_short(b+offset); + offset+=size; + return r; + } +} ; + +template<> struct dlp<long> +{ + enum { size = 4 }; + + static void append(pi_buffer_t *b, int v) + { + char buf[size]; + set_long(buf,v); + pi_buffer_append(b,buf,size); + } + + /** + * Returns the next long (4 byte) value from the buffer or + * -1 on error (which is also a valid value). + */ + static int read(const pi_buffer_t *b, unsigned int &offset) + { + if (offset+size > b->used) + { + return -1; + } + else + { + int r = get_long(b->data + offset); + offset+=size; + return r; + } + } + + /** + * Overload to read a long value from a data buffer; does + * no bounds checking. + */ + static int read(const unsigned char *b, unsigned int &offset) + { + int r = get_long(b+offset); + offset+=size; + return r; + } +} ; + +template<> struct dlp<char *> +{ + // No size enum, doesn't make sense + // No append, use pi_buffer_append + /** + * Read a fixed-length string from the buffer @p b into data buffer + * @p v which has size (including terminating NUL) of @p s. + * Returns the number of bytes read (which will normally be @p s + * but will be less than @p s on error). + */ + static int read(const pi_buffer_t *b, + unsigned int &offset, + unsigned char *v, + size_t s) + { + if ( s+offset > b->used ) + { + s = b->allocated - offset; + } + memcpy(v, b->data + offset, s); + offset+=s; + return s; + } + + /** Overload for signed char. */ + inline static int read(const pi_buffer_t *b, unsigned int &offset, char *v, size_t s) + { + return read(b,offset,(unsigned char *)v,s); + } +} ; + +} + +#endif + diff --git a/kpilot/lib/pilotAddress.cc b/kpilot/lib/pilotAddress.cc new file mode 100644 index 000000000..418c705b4 --- /dev/null +++ b/kpilot/lib/pilotAddress.cc @@ -0,0 +1,636 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2007 by Adriaan de Groot <groot@kde.org> +** +** This is a C++ wrapper for the pilot's address database structures. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "options.h" + + +#include <stdlib.h> +#include <assert.h> + +#include <qnamespace.h> +#include <qstringlist.h> + +#include "pilotAddress.h" + +static const char *default_address_category_names[] = { + "Unfiled", + "Business", + "Personal", + "Quicklist", + 0L +} ; + +static const char *default_address_field_labels[] = { + "Last name", + "First name", + "Company", + "Work", + "Home", + "Fax", + "Other", + "E-mail", + "Addr(W)", + "City", + "State", + "Zip Code", + "Country", + "Title", + "Custom 1", + "Custom 2", + "Custom 3", + "Custom 4", + "Note", + 0L +} ; + +void PilotAddressInfo::resetToDefault() +{ + FUNCTIONSETUP; + // Reset to all 0s + memset(&fInfo,0,sizeof(fInfo)); + // Fill up default categories + for (unsigned int i=0; (i<4) && default_address_category_names[i]; ++i) + { + strncpy(fInfo.category.name[i],default_address_category_names[i],sizeof(fInfo.category.name[0])); + } + // Weird hack, looks like there's an extra copy of Unfiled + strncpy(fInfo.category.name[15],default_address_category_names[0],sizeof(fInfo.category.name[0])); + + // And fill up the default labels. + for (unsigned int i=0; (i<19) && default_address_field_labels[i]; ++i) + { + strncpy(fInfo.labels[i],default_address_field_labels[i],sizeof(fInfo.labels[0])); + } +} + +QString PilotAddressInfo::phoneLabel(EPhoneType i) const +{ + if (i<=eMobile) + { + return Pilot::fromPilot(info()->phoneLabels[i]); + } + else + { + return QString(); + } +} + +PhoneSlot::PhoneSlot( const int v ) +{ + i = entryPhone1; + operator=(v); +} + +const PhoneSlot &PhoneSlot::operator=( const int &v ) +{ + if ( (entryPhone1 <= v) && (v <= entryPhone5) ) + { + i = v; + } + else + { + i = invalid; + } + return *this; +} + +const PhoneSlot &PhoneSlot::operator++() +{ + if ( (i!=invalid) && (i<entryPhone5) ) + { + ++i; + } + else + { + i = invalid; + } + return *this; +} + +/* static */ const PhoneSlot PhoneSlot::begin() +{ + return PhoneSlot( entryPhone1 ); +} + +/* static */ const PhoneSlot PhoneSlot::end() +{ + return PhoneSlot( invalid ); +} + +unsigned int PhoneSlot::toOffset() const +{ + if ( isValid() ) + { + return i-entryPhone1; + } + else + { + return 0; + } +} + +unsigned int PhoneSlot::toField() const +{ + if ( isValid() ) + { + return i; + } + else + { + return entryPhone1; + } +} + +PhoneSlot::operator QString() const +{ + return QString("%1,%2").arg(toOffset()).arg(toField()); +} + +#define MAXFIELDS 19 + +PilotAddress::PilotAddress(PilotRecord *rec) : + PilotRecordBase(rec), + fAddressInfo() +{ + FUNCTIONSETUPL(4); + memset(&fAddressInfo,0,sizeof(fAddressInfo)); + + if (rec) + { + pi_buffer_t b; + b.data = (unsigned char *) rec->data(); + b.allocated = b.used = rec->size(); + unpack_Address(&fAddressInfo, &b, address_v1); + } + else + { + fAddressInfo.phoneLabel[0] = (int) PilotAddressInfo::eWork; + fAddressInfo.phoneLabel[1] = (int) PilotAddressInfo::eHome; + fAddressInfo.phoneLabel[2] = (int) PilotAddressInfo::eOther; + fAddressInfo.phoneLabel[3] = (int) PilotAddressInfo::eMobile; + fAddressInfo.phoneLabel[4] = (int) PilotAddressInfo::eEmail; + } +} + +PilotAddress::PilotAddress(const PilotAddress & copyFrom) : + PilotRecordBase(copyFrom), + fAddressInfo() +{ + FUNCTIONSETUPL(4); + _copyAddressInfo(copyFrom.fAddressInfo); +} + +PilotAddress & PilotAddress::operator = (const PilotAddress & copyFrom) +{ + FUNCTIONSETUPL(4); + PilotRecordBase::operator = (copyFrom); + _copyAddressInfo(copyFrom.fAddressInfo); + return *this; +} + +bool PilotAddress::operator==(const PilotAddress &compareTo) +{ + FUNCTIONSETUPL(4); + + // now compare all the fields stored in the fAddressInfo.entry array of char*[19] + for (int i=0; i<MAXFIELDS; i++) { + // if one is NULL, and the other non-empty, they are not equal for sure + if ( !getFieldP(i) && compareTo.getFieldP(i)) + { + return false; + } + if ( getFieldP(i) && !compareTo.getFieldP(i)) + { + return false; + } + + // test for getField(i)!=... to prevent strcmp or NULL strings! None or both can be zero, but not a single one. + if ( (getFieldP(i) != compareTo.getFieldP(i)) && ( strcmp(getFieldP(i), compareTo.getFieldP(i)) ) ) + { + return false; + } + } + return true; +} + + +void PilotAddress::_copyAddressInfo(const struct Address ©From) +{ + FUNCTIONSETUPL(4); + fAddressInfo.showPhone = copyFrom.showPhone; + + for (int labelLp = 0; labelLp < 5; labelLp++) + { + fAddressInfo.phoneLabel[labelLp] = + copyFrom.phoneLabel[labelLp]; + } + + for (unsigned int i = 0; i< MAXFIELDS; ++i) + { + if (copyFrom.entry[i]) + { + fAddressInfo.entry[i] = qstrdup(copyFrom.entry[i]); + } + else + { + fAddressInfo.entry[i] = 0L; + } + } +} + + +PilotAddress::~PilotAddress() +{ + FUNCTIONSETUPL(4); + free_Address(&fAddressInfo); +} + +QString PilotAddress::getTextRepresentation(const PilotAddressInfo *info, Qt::TextFormat richText) const +{ + QString text, tmp; + + QString par = (richText==Qt::RichText) ?CSL1("<p>"): QString(); + QString ps = (richText==Qt::RichText) ?CSL1("</p>"):CSL1("\n"); + QString br = (richText==Qt::RichText) ?CSL1("<br/>"):CSL1("\n"); + + // title + name + text += par; + if (!getField(entryTitle).isEmpty()) + { + text += rtExpand(getField(entryTitle), richText); + text += CSL1(" "); + } + + tmp = richText ? CSL1("<b><big>%1 %2</big></b>") : CSL1("%1 %2"); + QString firstName = getField(entryFirstname); + if (firstName.isEmpty()) + { + // So replace placeholder for first name (%1) with empty + tmp = tmp.arg(QString()); + } + else + { + tmp = tmp.arg(rtExpand(firstName,richText)); + } + tmp=tmp.arg(rtExpand(getField(entryLastname), richText)); + text += tmp; + text += ps; + + // company + if (!getField(entryCompany).isEmpty()) + { + text += par; + text += rtExpand(getField(entryCompany), richText); + text += ps; + } + + // phone numbers (+ labels) + text += par; + for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i ) + { + if (!getField(i.toField()).isEmpty()) + { + if (richText) + { + if (getShownPhone() == i) + { + tmp=CSL1("<small>%1: </small><b>%2</b>"); + } + else + { + tmp=CSL1("<small>%1: </small>%2"); + } + } + else + { + tmp=CSL1("%1: %2"); + } + if (info) + { + tmp=tmp.arg(info->phoneLabel( getPhoneType( i ) )); + } + else + { + tmp=tmp.arg(CSL1("Contact: ")); + } + tmp=tmp.arg(rtExpand(getField(i.toField()), richText)); + text += tmp; + text += br; + } + } + text += ps; + + // address, city, state, country + text += par; + if (!getField(entryAddress).isEmpty()) + { + text += rtExpand(getField(entryAddress), richText); + text += br; + } + if (!getField(entryCity).isEmpty()) + { + text += rtExpand(getField(entryCity), richText); + text += CSL1(" "); + } + if (!getField(entryState).isEmpty()) + { + text += rtExpand(getField(entryState), richText); + text += CSL1(" "); + } + if (!getField(entryZip).isEmpty()) + { + text += rtExpand(getField(entryZip), richText); + } + text += br; + if (!getField(entryCountry).isEmpty()) + { + text += rtExpand(getField(entryCountry), richText); + text += br; + } + text += ps; + + // custom fields + text += par; + for (int i = entryCustom1; i <= entryCustom4; i++) + { + if (!getField(i).isEmpty()) + { + text += rtExpand(getField(i), richText); + text += br; + } + } + text += ps; + + // category + if (info) + { + QString categoryName = info->categoryName( category() ); + if (!categoryName.isEmpty()) + { + text += par; + text += rtExpand(categoryName, richText); + text += ps; + } + } + + // note + if (!getField(entryNote).isEmpty()) + { + text += richText?CSL1("<hr/>"):CSL1("-----------------------------\n"); + text += par; + text += rtExpand(getField(entryNote), richText); + text += ps; + } + + return text; +} + +QStringList PilotAddress::getEmails() const +{ + QStringList list; + + for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i) + { + PilotAddressInfo::EPhoneType t = getPhoneType( i ); + if ( t == PilotAddressInfo::eEmail ) + { + QString s = getField(i.toField()); + if (!s.isEmpty()) + { + list.append(s); + } + } + } + + return list; +} + +void PilotAddress::setEmails(const QStringList &list) +{ + FUNCTIONSETUPL(4); + QString test; + + // clear all e-mails first + for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i ) + { + PilotAddressInfo::EPhoneType t = getPhoneType( i ); + if (t == PilotAddressInfo::eEmail) + { + setField(i.toField(), QString() ); + } + } + + for(QStringList::ConstIterator listIter = list.begin(); + listIter != list.end(); ++listIter) + { + QString email = *listIter; + if (!setPhoneField(PilotAddressInfo::eEmail, email, NoFlags).isValid()) + { + WARNINGKPILOT << "Email accounts overflowed, silently dropped." << endl; + } + } +} + +QString PilotAddress::getField(int field) const +{ + if ( (entryLastname <= field) && (field <= entryNote) ) + { + return Pilot::fromPilot(fAddressInfo.entry[field]); + } + else + { + return QString(); + } +} + +PhoneSlot PilotAddress::_getNextEmptyPhoneSlot() const +{ + FUNCTIONSETUPL(4); + for (PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i) + { + const char *phoneField = getFieldP(i.toField()); + + if (!phoneField || !phoneField[0]) + { + return i; + } + } + return PhoneSlot(); +} + +PhoneSlot PilotAddress::setPhoneField(PilotAddressInfo::EPhoneType type, + const QString &field, + PhoneHandlingFlags flags) +{ + FUNCTIONSETUPL(4); + + const bool overwriteExisting = (flags == Replace); + PhoneSlot fieldSlot; + if (overwriteExisting) + { + fieldSlot = _findPhoneFieldSlot(type); + } + + if ( !fieldSlot.isValid() ) + { + fieldSlot = _getNextEmptyPhoneSlot(); + } + + // store the overflow phone + if ( !fieldSlot.isValid() ) + { + DEBUGKPILOT << fname << ": Phone would overflow." << endl; + } + else // phone field 1 - 5; straight forward storage + { + setField(fieldSlot.toField(), field); + fAddressInfo.phoneLabel[fieldSlot.toOffset()] = (int) type; + } + return fieldSlot; +} + +PhoneSlot PilotAddress::_findPhoneFieldSlot(PilotAddressInfo::EPhoneType t) const +{ + FUNCTIONSETUPL(4); + for ( PhoneSlot i = PhoneSlot::begin(); i.isValid(); ++i ) + { + if ( getPhoneType(i) == t ) + { + return i; + } + } + + return PhoneSlot(); +} + +QString PilotAddress::getPhoneField(PilotAddressInfo::EPhoneType type) const +{ + FUNCTIONSETUPL(4); + PhoneSlot fieldSlot = _findPhoneFieldSlot(type); + + if (fieldSlot.isValid()) + { + return getField(fieldSlot.toField()); + } + + return QString(); +} + +PhoneSlot PilotAddress::getShownPhone() const +{ + // The slot is stored as an offset + return PhoneSlot(entryPhone1 + fAddressInfo.showPhone); +} + +const PhoneSlot &PilotAddress::setShownPhone( const PhoneSlot &v ) +{ + FUNCTIONSETUPL(4); + if (v.isValid()) + { + fAddressInfo.showPhone = v.toOffset(); + } + return v; +} + +PhoneSlot PilotAddress::setShownPhone(PilotAddressInfo::EPhoneType type) +{ + FUNCTIONSETUPL(4); + PhoneSlot fieldSlot = _findPhoneFieldSlot(type); + + // Did we find a slot with the requested type? + if (!fieldSlot.isValid()) + { + // No, so look for first non-empty phone slot + for ( fieldSlot = PhoneSlot::begin(); fieldSlot.isValid(); ++fieldSlot ) + { + const char *p = getFieldP(fieldSlot.toField()); + if (p && p[0]) + { + break; + } + } + // If all of them are empty, then use first slot instead + if (!fieldSlot.isValid()) + { + fieldSlot = PhoneSlot::begin(); + } + } + setShownPhone(fieldSlot); + return fieldSlot; +} + +PilotAddressInfo::EPhoneType PilotAddress::getPhoneType( const PhoneSlot &field ) const +{ + if ( field.isValid() ) + { + return (PilotAddressInfo::EPhoneType) fAddressInfo.phoneLabel[field.toOffset()]; + } + else + { + return PilotAddressInfo::eNone; + } +} + +void PilotAddress::setField(int field, const QString &text) +{ + FUNCTIONSETUPL(4); + // This will have either been created with unpack_Address, and/or will + // be released with free_Address, so use malloc/free here: + if (fAddressInfo.entry[field]) + { + free(fAddressInfo.entry[field]); + fAddressInfo.entry[field]=0L; + } + if (!text.isEmpty()) + { + fAddressInfo.entry[field] = (char *) malloc(text.length() + 1); + Pilot::toPilot(text, fAddressInfo.entry[field], text.length()+1); + } + else + { + fAddressInfo.entry[field] = 0L; + } +} + +PilotRecord *PilotAddress::pack() const +{ + FUNCTIONSETUPL(4); + int i; + + pi_buffer_t *b = pi_buffer_new( sizeof(fAddressInfo) ); + i = pack_Address(const_cast<Address_t *>(&fAddressInfo), b, address_v1); + if (i<0) + { + return 0L; + } + // pack_Address sets b->used + return new PilotRecord( b, this ); +} diff --git a/kpilot/lib/pilotAddress.h b/kpilot/lib/pilotAddress.h new file mode 100644 index 000000000..d6dd20e45 --- /dev/null +++ b/kpilot/lib/pilotAddress.h @@ -0,0 +1,339 @@ +#ifndef _KPILOT_PILOTADDRESS_H +#define _KPILOT_PILOTADDRESS_H +/* pilotAddress.h KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2007 by Adriaan de Groot <groot@kde.org> +** +** This is a wrapper for pilot-link's address structures. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <pi-macros.h> +#include <pi-address.h> + +#include <qnamespace.h> + +#include "pilotRecord.h" +#include "pilotAppInfo.h" + +/** Interpreted form of the AppInfo block in the address database. */ +typedef PilotAppInfo< + AddressAppInfo, + unpack_AddressAppInfo, + pack_AddressAppInfo> PilotAddressInfo_; + +/** This class exists @em only to clear up the type mess that +* is the field-numbers-and-indexes for phone numbers in the +* handheld records. The standard address record has 19 fields, +* five of which are phone fields. Those are fields 3..7 and they +* are referred to as fields Phone1 .. Phone5. Sometimes we +* need to act as if the phone field numbers are indeed the field +* numbers (3..7) and sometimes we need to use those same field +* numbers to index into a C array (0 based!) so then we map +* field number 3 (Phone1) to a 0 index. +* +* Also handles iteration nicely. +* +* A phone slot value may be invalid. If so, operations on it will +* fail (yielding invalid again) and isValid() will return @c false. +*/ +class PhoneSlot +{ +friend class PilotAddress; +protected: + /** Constructor. Use the specified value for the phone slot. + * @p v is a field number (3..8). + */ + explicit PhoneSlot( const int v ); + + /** Assignment operator. Set the value of the slot to + * the specified value @p v . This may yield an invalid + * phone slot. + */ + const PhoneSlot &operator=(const int &v); + + /** Map the slot to an offset (for use in finding the phone type + * for a given slot). + * @return Offset of this slot within the phone fields. + */ + unsigned int toOffset() const; + + /** Map the slot to a field number. */ + unsigned int toField() const; + +public: + static const int invalid = -1; ///< Value for invalid slots. */ + + /** Constructor. The slot is invalid. */ + PhoneSlot() + { + i = invalid; + } + + /** Comparison operator. */ + bool operator ==( const PhoneSlot &v ) const + { + return v.i == i; + } + + /** Iterator operation. Go to the next slot (or invalid when + * the range runs out). + */ + const PhoneSlot &operator++(); + + /** Begin value of an iteration through the phone slots. */ + static const PhoneSlot begin(); + + /** When the slot range runs out (past entryPhone5) it + * is invalid, so the end compares with that. + */ + static const PhoneSlot end(); + + /** Valid slots are entryPhone1 (3) through entryPhone5 (7). + * @return @c true if the slot is valid. + */ + bool isValid() const + { + return (entryPhone1 <= i) && (i <= entryPhone5); + } + + operator QString() const; +private: + int i; +} ; + + +class PilotAddressInfo : public PilotAddressInfo_ +{ +public: + PilotAddressInfo(PilotDatabase *d) : PilotAddressInfo_(d) + { + } + + /** This resets the entire AppInfo block to one as it would be + * in an English-language handheld, with 3 categories and + * default field labels for everything. + */ + void resetToDefault(); + + enum EPhoneType { + eWork=0, + eHome, + eFax, + eOther, + eEmail, + eMain, + ePager, + eMobile, + eNone=-1 + } ; + + QString phoneLabel(EPhoneType i) const; +} ; + +/** @brief A wrapper class around the Address struct provided by pi-address.h + * + * This class allows the user to set and get address field values. + * For everything but phone fields, the user can simply pass the + * the pi-address enum for the index for setField() and getField() such + * as entryLastname. + * + * Phone fields are a bit trickier. The structure allows for 8 possible + * phone fields with 5 possible slots. That means there could be three + * fields that don't have available storage. The setPhoneField() method + * will attempt to store the extra fields in a custom field if there + * is an overflow. + * + * There are eight possible fields for 5 view slots: + * - fields: Work, Home, Fax, Other, Pager, Mobile, E-mail, Main + * - slots: entryPhone1, entryPhone2, entryPhone3, entryPhone4, entryPhone5 + * + * Internally in the pilot-link library, the AddressAppInfo phone + * array stores the strings for the eight possible phone values. + * Their English string values are : + * - phone[0] = Work + * - phone[1] = Home + * - phone[2] = Fax + * - phone[3] = Other + * - phone[4] = E-mail + * - phone[5] = Main + * - phone[6] = Pager + * - phone[7] = Mobile + * + * Apparently, this order is kept for all languages, just with localized + * strings. The implementation of the internal methods will assume + * this order is kept. In other languages, main can replaced with + * Corporation. + */ +class KDE_EXPORT PilotAddress : public PilotRecordBase +{ +public: + PilotAddress(PilotRecord *rec = 0L); + PilotAddress(const PilotAddress ©From); + PilotAddress& operator=( const PilotAddress &r ); + bool operator==(const PilotAddress &r); + + virtual ~PilotAddress(); + + /** Returns a text representation of the address. If @p richText is true, the + * text will be formatted with Qt-HTML tags. The AppInfo structure @p info + * is used to figure out the phone labels; if it is NULL then bogus labels are + * used to identify phone types. + */ + QString getTextRepresentation(const PilotAddressInfo *info, Qt::TextFormat richText) const; + + /** + * @param text set the field value + * @param field int values associated with the enum defined in + * pi-address.h. + * The copied possible enum's are: (copied from pi-address.h on 1/12/01) + * enum { entryLastname, entryFirstname, entryCompany, + * entryPhone1, entryPhone2, entryPhone3, entryPhone4, entryPhone5, + * entryAddress, entryCity, entryState, entryZip, entryCountry, + * entryTitle, entryCustom1, entryCustom2, entryCustom3, entryCustom4, + * entryNote }; + */ + void setField(int field, const QString &text); + /** Set a field @p i to a given text value. Uses the phone slots only. */ + void setField(const PhoneSlot &i, const QString &t) + { + if (i.isValid()) + { + setField(i.toField(),t); + } + } + + /** Returns the text value of a given field @p field (or QString::null + * if there is no such field). + */ + QString getField(int field) const; + /** Returns the value of the phone field @p i . */ + QString getField(const PhoneSlot &i) const + { + return i.isValid() ? getField(i.toField()) : QString(); + } + + /** + * Return list of all email addresses. This will search through our "phone" + * fields and will return only those which are e-mail addresses. + */ + QStringList getEmails() const; + void setEmails(const QStringList &emails); + + enum PhoneHandlingFlags + { + NoFlags=0, ///< No special handling + Replace ///< Replace existing entries of same type + } ; + + /** + * @param type is the type of phone + * @param checkCustom4 flag if true, checks the entryCustom4 field + * for extra phone fields + * @return the field associated with the type + */ + QString getPhoneField(PilotAddressInfo::EPhoneType type) const; + + /** + * @param type is the type of phone + * @param field is value to store + * @param overflowCustom is true, and entryPhone1 to entryPhone5 is full + * it will use entryCustom4 field to store the field + * @param overwriteExisting is true, it will overwrite an existing record-type + * with the field, else it will always search for the first available slot + * @return index of the field that this information was set to + */ + PhoneSlot setPhoneField(PilotAddressInfo::EPhoneType type, const QString &value, PhoneHandlingFlags flags); + + /** + * Returns the slot of the phone number + * selected by the user to be shown in the + * overview of addresses. + * + * @return Slot of phone entry (between entryPhone1 and entryPhone5) + */ + PhoneSlot getShownPhone() const; + + /** + * Set the shown phone (the one preferred by the user for display + * on the handheld's overview page) to the @em type (not index) + * indicated. Looks through the phone entries of this record to + * find the first one one of this type. + * + * @return Slot of phone entry. + * + * @note Sets the shown phone to the first entry if no field of + * type @p phoneType can be found @em and no Home phone + * field (the fallback) can be found either. + */ + PhoneSlot setShownPhone(PilotAddressInfo::EPhoneType phoneType); + + /** + * Set the shown phone (the one preferred by the user for display + * on the handheld's overview page) to the given @p slot . + * + * @return @p v + */ + const PhoneSlot &setShownPhone(const PhoneSlot &v); + + /** Get the phone type (label) for a given field @p field + * in the record. The @p field must be within the + * phone range (entryPhone1 .. entryPhone5). + * + * @return Phone type for phone field @p field . + * @return @c eNone (fake phone type) if @p field is invalid. + */ + PilotAddressInfo::EPhoneType getPhoneType(const PhoneSlot &field) const; + + PilotRecord *pack() const; + + const struct Address *address() const { return &fAddressInfo; } ; + + +protected: + // Get the pointers in cases where no conversion to + // unicode is desired. + // + const char *getFieldP(int field) const + { + return fAddressInfo.entry[field]; + } + +private: + void _copyAddressInfo(const struct Address ©From); + PhoneSlot _getNextEmptyPhoneSlot() const; + + /** @return entryPhone1 to entryPhone5 if the appTypeNum number is + * found in the phoneLabel array; return -1 if not found + */ + PhoneSlot _findPhoneFieldSlot(PilotAddressInfo::EPhoneType t) const; + + struct Address fAddressInfo; +}; + + + + +#endif diff --git a/kpilot/lib/pilotAppInfo.cc b/kpilot/lib/pilotAppInfo.cc new file mode 100644 index 000000000..e8caf6234 --- /dev/null +++ b/kpilot/lib/pilotAppInfo.cc @@ -0,0 +1,77 @@ +/* pilotAppInfo.cc KPilot +** +** Copyright (C) 2005-2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "options.h" + +#include <stdio.h> + +#include "pilotAppInfo.h" + +PilotAppInfoBase::PilotAppInfoBase(PilotDatabase *d) : + fC( 0L ), + fLen(0), + fOwn(true) +{ + FUNCTIONSETUP; + int appLen = Pilot::MAX_APPINFO_SIZE; + unsigned char buffer[Pilot::MAX_APPINFO_SIZE]; + + if (!d || !d->isOpen()) + { + WARNINGKPILOT << "Bad database pointer." << endl; + fLen = 0; + KPILOT_DELETE( fC ); + return; + } + + fC = new struct CategoryAppInfo; + fLen = appLen = d->readAppBlock(buffer,appLen); + unpack_CategoryAppInfo(fC, buffer, appLen); +} + +PilotAppInfoBase::~PilotAppInfoBase() +{ + if (fOwn) + { + delete fC; + } +} + +bool PilotAppInfoBase::setCategoryName(unsigned int i, const QString &s) +{ + if ( (i>=Pilot::CATEGORY_COUNT) || // bad category number + (!categoryInfo())) // Nowhere to write to + { + return false; + } + + (void) Pilot::toPilot(s, categoryInfo()->name[i], Pilot::CATEGORY_SIZE - 1); + return true; +} + + diff --git a/kpilot/lib/pilotAppInfo.h b/kpilot/lib/pilotAppInfo.h new file mode 100644 index 000000000..d5db9e358 --- /dev/null +++ b/kpilot/lib/pilotAppInfo.h @@ -0,0 +1,216 @@ +#ifndef _KPILOT_PILOTAPPINFO_H +#define _KPILOT_PILOTAPPINFO_H +/* pilotAppInfo.h KPilot +** +** Copyright (C) 2005-2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "pilotLinkVersion.h" + +#include "pilot.h" +#include "pilotDatabase.h" + +/** +* A database on the handheld has an "AppInfo" block at the beginning +* with some database-specific information and a common part. +* This base class deals with the common part, the categories. +* +* Most data in the handheld is stored in @em categories ; every +* record in every database, for instance, has a category assigned +* to it (perhaps "Unfiled", but that's just another category). +* +* Every database has a category table assigning labels to the +* categories that exist. There are CATEGORY_COUNT (16) categories +* available for each database; labels may vary per database. +* +* This class encapsulates the basic category table manipulations. +*/ +class KDE_EXPORT PilotAppInfoBase +{ +protected: + /** Initialize class members after reading header, to alias data elsewhere. + * Only for use by the (derived) template classes below. + */ + void init(struct CategoryAppInfo *c, int len) + { + fC = c; + fLen = len ; + } ; + +public: + /** Constructor. This is for use by derived classes (using the template below + * only, and says that the category info in the base class aliases data in + * the derived class. Remember to call init()! + */ + PilotAppInfoBase() : fC(0L), fLen(0), fOwn(false) { } ; + + /** Constructor, intended for untyped access to the AppInfo only. This throws + * away everything but the category information. In this variety, the + * CategoryAppInfo structure is owned by the PilotAppInfoBase object. + */ + PilotAppInfoBase(PilotDatabase *d); + + /** Destructor. */ + virtual ~PilotAppInfoBase(); + + /** Retrieve the most basic part of the AppInfo block -- the category + * information which is guaranteed to be the first 240-odd bytes of + * a database. + */ + struct CategoryAppInfo *categoryInfo() + { + return fC; + } ; + + /** Const version of the above function. */ + inline const struct CategoryAppInfo *categoryInfo() const + { + return fC; + } ; + + /** Returns the length of the (whole) AppInfo block. */ + inline PI_SIZE_T length() const + { + return fLen; + } ; + + /** @see findCategory(const QString &name, bool unknownIsUnfiled, struct CategoryAppInfo *info). */ + inline int findCategory(const QString &name, bool unknownIsUnfiled = false) const + { + return Pilot::findCategory(fC,name,unknownIsUnfiled); + } ; + + /** Gets a single category name. Returns QString::null if there is no + * such category number @p i . */ + inline QString categoryName(unsigned int i) const + { + return Pilot::categoryName(fC,i); + } + + /** Sets a category name. @return true if this succeeded. @return false + * on failure, e.g. the index @p i was out of range or the category name + * was invalid. Category names that are too long are truncated to 15 characters. + */ + bool setCategoryName(unsigned int i, const QString &s); + + /** For debugging, display all the category names */ + inline void dump() const + { + Pilot::dumpCategories(fC); + }; + +protected: + struct CategoryAppInfo *fC; + PI_SIZE_T fLen; + + bool fOwn; +} ; + +/** A template class for reading and interpreting AppInfo blocks; +* the idea is that it handles all the boilerplate code for reading +* the app block, converting it to the right kind, and then unpacking +* it. Template parameters are the type (struct, from pilot-link probably) +* of the interpreted appinfo, and the pack and unpack functions for it +* (again, from pilot-link). +*/ +template <typename appinfo, +#if PILOT_LINK_IS(0,12,2) + /* There are additional consts introduced in 0.12.2 */ + int(*unpack)(appinfo *, const unsigned char *, PI_SIZE_T), + int(*pack)(const appinfo *, unsigned char *, PI_SIZE_T) +#else + int(*unpack)(appinfo *, unsigned char *, PI_SIZE_T), + int(*pack)(appinfo *, unsigned char *, PI_SIZE_T) +#endif + > +class PilotAppInfo : public PilotAppInfoBase +{ +public: + /** Constructor. Read the appinfo from database @p d and + * interpret it. + */ + PilotAppInfo(PilotDatabase *d) : PilotAppInfoBase() + { + int appLen = Pilot::MAX_APPINFO_SIZE; + unsigned char buffer[Pilot::MAX_APPINFO_SIZE]; + + memset(&fInfo,0,sizeof(fInfo)); + if (d && d->isOpen()) + { + appLen = d->readAppBlock(buffer,appLen); + (*unpack)(&fInfo, buffer, appLen); + // fInfo is just a struct, so we can point to it anyway. + init(&fInfo.category,appLen); + } + else + { + delete fC; + fC = 0L; + fLen = 0; + init(&fInfo.category,sizeof(fInfo)); + } + } ; + + PilotAppInfo() + { + memset(&fInfo,0,sizeof(fInfo)); + init(&fInfo.category,sizeof(fInfo)); + } + + + /** Write this appinfo block to the database @p d; returns + * the number of bytes written or -1 on failure. This + * function is robust when called with a NULL database @p d. + */ + int writeTo(PilotDatabase *d) + { + unsigned char buffer[Pilot::MAX_APPINFO_SIZE]; + if (!d || !d->isOpen()) + { + return -1; + } + int appLen = (*pack)(&fInfo, buffer, length()); + if (appLen > 0) + { + d->writeAppBlock(buffer,appLen); + } + return appLen; + } ; + + /** Returns a (correctly typed) pointer to the interpreted + * appinfo block. + */ + appinfo *info() { return &fInfo; } ; + /** Returns a const (correctly typed) pointer to the interpreted + * appinfo block. + */ + const appinfo *info() const { return &fInfo; } ; + +protected: + appinfo fInfo; +} ; + + +#endif diff --git a/kpilot/lib/pilotCard.h b/kpilot/lib/pilotCard.h new file mode 100644 index 000000000..86d1f70c6 --- /dev/null +++ b/kpilot/lib/pilotCard.h @@ -0,0 +1,65 @@ +#ifndef _KPILOT_PILOTCARD_H +#define _KPILOT_PILOTCARD_H +/* pilotCard.h KPilot +** +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This class is a wrapper around pilot-link's CardInfo structure +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifndef _PILOT_DLP_H_ +#include <pi-dlp.h> +#endif + +class KPilotCard +{ +public: + KPilotCard() { ::memset(&fCard,0,sizeof(struct CardInfo)); } + KPilotCard(const CardInfo* card) { fCard = *card; } + + CardInfo *cardInfo() { return &fCard; } + + /** + * Ensures the names are properly terminated. Needed incase we + * are syncing a new and bogus pilot. + */ + void boundsCheck() {} + + const int getCardIndex() const { return fCard.card; } + const int getCardVersion() const { return fCard.version; } + unsigned long getRomSize() const { return fCard.romSize; } + unsigned long getRamSize() const { return fCard.ramSize; } + unsigned long getRamFree() const { return fCard.ramFree; } + const char* getCardName() const { return fCard.name; } + const char* getCardManufacturer() const { return fCard.manufacturer; } + +private: + struct CardInfo fCard; +}; + +#endif diff --git a/kpilot/lib/pilotDatabase.cc b/kpilot/lib/pilotDatabase.cc new file mode 100644 index 000000000..fd568b703 --- /dev/null +++ b/kpilot/lib/pilotDatabase.cc @@ -0,0 +1,112 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2005-2006 Adriaan de Groot <groot@kde.org> +** +** This is the abstract base class for databases, which is used both +** by local databases and by the serial databases held in the Pilot. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <time.h> // Needed by pilot-link include +#include <pi-appinfo.h> + +#include <qstringlist.h> + +#include <kglobal.h> + +#include "pilotDatabase.h" +#include "pilotRecord.h" + +static int creationCount = 0; +static QStringList *createdNames = 0L; + +PilotDatabase::PilotDatabase(const QString &s) : + fDBOpen(false), + fName(s) +{ + FUNCTIONSETUP; + creationCount++; + if (!createdNames) + { + createdNames = new QStringList(); + } + createdNames->append(s.isEmpty() ? CSL1("<empty>") : s); +} + +/* virtual */ PilotDatabase::~PilotDatabase() +{ + FUNCTIONSETUP; + creationCount--; + if (createdNames) + { + createdNames->remove(fName.isEmpty() ? CSL1("<empty>") : fName); + } +} + +/* static */ int PilotDatabase::instanceCount() +{ + FUNCTIONSETUP; + DEBUGKPILOT << fname << ": " << creationCount << " databases." << endl; + if (createdNames) + { + DEBUGKPILOT << fname << ": " + << createdNames->join(CSL1(",")) << endl; + } + return creationCount; +} + +/* virtual */ Pilot::RecordIDList PilotDatabase::idList() +{ + Pilot::RecordIDList l; + + for (unsigned int i = 0 ; ; i++) + { + PilotRecord *r = readRecordByIndex(i); + if (!r) break; + l.append(r->id()); + delete r; + } + + return l; +} + +/* virtual */ Pilot::RecordIDList PilotDatabase::modifiedIDList() +{ + Pilot::RecordIDList l; + + resetDBIndex(); + while(1) + { + PilotRecord *r = readNextModifiedRec(); + if (!r) break; + l.append(r->id()); + delete r; + } + + return l; +} + diff --git a/kpilot/lib/pilotDatabase.h b/kpilot/lib/pilotDatabase.h new file mode 100644 index 000000000..84c922ed4 --- /dev/null +++ b/kpilot/lib/pilotDatabase.h @@ -0,0 +1,272 @@ +#ifndef _KPILOT_PILOTDATABASE_H +#define _KPILOT_PILOTDATABASE_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2005-2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "pilot.h" + + +/** @file +* This is the abstract base class for databases, which is used both +* by local databases and by the serial databases held in the Pilot. +*/ + + +/** + * Methods to access a database on the pilot. + * + * NOTE: It is the users responsibility + * to delete PilotRecords returned by + * PilotDatabase methods when finished with them! + */ + +class KDE_EXPORT PilotDatabase +{ +public: + PilotDatabase(const QString &name = QString::null); + virtual ~PilotDatabase(); + + + QString name() const { return fName; } ; + + /** + * Debugging information: tally how many databases are created + * or destroyed. Returns the count of currently existing databases. + */ + static int instanceCount(); + + /* -------------------- Abstract interface for subclasses ----------------- */ + + /** + * Creates the database with the given creator, type and flags + * on the given card (default is RAM). If the database already + * exists, this function does nothing. + */ + virtual bool createDatabase(long creator=0, long type=0, + int cardno=0, int flags=0, int version=0) = 0; + + /** + * Deletes the database (by name, as given in the constructor, + * the database name is stored depending on the implementation + * of PilotLocalDatabase and PilotSerialDatabas) + */ + virtual int deleteDatabase()=0; + + /** Reads the application block info, returns size. */ + virtual int readAppBlock(unsigned char* buffer, int maxLen) = 0; + + /** Writes the application block info. */ + virtual int writeAppBlock(unsigned char* buffer, int len) = 0; + + /** Returns the number of records in the database. + * If the database is not open, return -1. + */ + virtual unsigned int recordCount() const=0; + + /** Returns a QValueList of all record ids in the database. + This implementation is really bad. */ + virtual Pilot::RecordIDList idList(); + + /** Returns a list of all record ids that have been modified in the + database. This implementation is really bad. */ + virtual Pilot::RecordIDList modifiedIDList(); + + + /** Reads a record from database by id, returns record length */ + virtual PilotRecord* readRecordById(recordid_t id) = 0; + + /** Reads a record from database, returns the record length */ + virtual PilotRecord* readRecordByIndex(int index) = 0; + + /** Reads the next record from database in category 'category' */ + virtual PilotRecord* readNextRecInCategory(int category) = 0; + + /** + * Reads the next record from database that has the dirty flag set. + * If @p ind is non-NULL, *ind is set to the index of the current + * record (i.e. before the record pointer moves to the next + * modified record). + */ + virtual PilotRecord* readNextModifiedRec(int *ind=NULL) = 0; + + /** + * Writes a new record to database (if 'id' == 0, one will be + * assigned to newRecord) + */ + virtual recordid_t writeRecord(PilotRecord* newRecord) = 0; + + /** + * Deletes a record with the given recordid_t from the database, + * or all records, if @p all is set to true. The recordid_t will + * be ignored in this case. + * + * Return value is negative on error, 0 otherwise. + */ + virtual int deleteRecord(recordid_t id, bool all=false) = 0; + + /** Resets all records in the database to not dirty. */ + virtual int resetSyncFlags() = 0; + + /** Resets next record index to beginning */ + virtual int resetDBIndex() = 0; + + /** Purges all Archived/Deleted records from Palm Pilot database */ + virtual int cleanup() = 0; + + bool isOpen() const { return fDBOpen; } + + /** Returns some sensible human-readable identifier for + * the database. Serial databases get Pilot:, local + * databases return the full path. + */ + virtual QString dbPathName() const = 0; + + /** + * Use this instead of RTTI to determine the type of a + * PilotDatabase, for those cases where it's important. + */ + typedef enum { eNone=0, + eLocalDB=1, + eSerialDB=2 } DBType; + virtual DBType dbType() const = 0; + + static inline bool isResource(struct DBInfo *info) + { + return (info->flags & dlpDBFlagResource); + } + +protected: + virtual void openDatabase() = 0; + virtual void closeDatabase() = 0; + + void setDBOpen(bool yesno) { fDBOpen = yesno; } + +private: + bool fDBOpen; + QString fName; +}; + +/** A template class for reading and interpreting a database. This removes +* the need for a lot of boilerplate code that does the conversions. +* Parameters are two interpretation classes: one for the KDE side of +* things (e.g. Event) and one that interprets the Pilot's records into +* a more sensible structure (e.g. PilotDatebookEntry). The mapping from +* the KDE type to the Pilot type and vice-versa is done by the mapper +* class's convert() functions. +* +* To interpret a database as pilot-link interpretations (e.g. as +* PilotDatebookEntry records, not as Events) use the NullMapper class +* below in combination with a template instantiation with kdetype==pilottype. +* +* The database interpreter intentionally has an interface similar to +* that of a PilotDatabase, but it isn't one. +*/ +template <class kdetype, class pilottype, class mapper> +class DatabaseInterpreter +{ +private: + /** Interpret a PilotRecord as an object of type kdetype. */ + kdetype *interpret(PilotRecord *r) + { + // NULL records return NULL kde objects. + if (!r) return 0; + // Interpret the binary blob as a pilot-link object. + pilottype *a = new pilottype(r); + // The record is now obsolete. + delete r; + // Interpretation failed. + if (!a) { return 0; } + // Now convert to KDE type. + kdetype *t = mapper::convert(a); + // The NULL mapper just returns the pointer a, so we + // need to check if anything has changed before deleting. + if ( (void *)t != (void *)a ) + { + delete a; + } + return t; + } +public: + /** Constructor. Interpret the database @p d. */ + DatabaseInterpreter(PilotDatabase *d) : fDB(d) { } ; + + /** Reads a record from database by @p id */ + kdetype *readRecordById(recordid_t id) + { + return interpret(fDB->readRecordById(id)); + } + + /** Reads a record from database with index @p index */ + kdetype *readRecordByIndex(int index) + { + return interpret(fDB->readRecordByIndex(index)); + } + + /** Reads the next record from database in category @p category */ + kdetype *readNextRecInCategory(int category) + { + return interpret(fDB->readNextRecInCategory(category)); + } + + /** + * Reads the next record from database that has the dirty flag set. + * If @p ind is non-NULL, *ind is set to the index of the current + * record (i.e. before the record pointer moves to the next + * modified record). + */ + kdetype *readNextModifiedRec(int *ind=NULL) + { + return interpret(fDB->readNextModifiedRec(ind)); + } + + + /** Retrieve the database pointer; this is useful to just pass + * around DatabaseInterpreter objects as if they are databases, + * and then perform DB operations on the database it wraps. + */ + PilotDatabase *db() const { return fDB; } + +protected: + PilotDatabase *fDB; +} ; + +/** NULL mapper class; the conversions here don't @em do anything, +* so you can use this when you only need 1 conversion step (from +* PilotRecord to PilotDatebookEntry, for instance) instead of 2. +*/ +template <class T> +class NullMapper +{ +public: + /** NULL Conversion function. */ + static T *convert(T *t) { return t; } +} ; + +#endif diff --git a/kpilot/lib/pilotDateEntry.cc b/kpilot/lib/pilotDateEntry.cc new file mode 100644 index 000000000..4fa93eac6 --- /dev/null +++ b/kpilot/lib/pilotDateEntry.cc @@ -0,0 +1,478 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This is a C++ wrapper for the Pilot's datebook structures. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <stdlib.h> + +#include <qdatetime.h> +#include <qnamespace.h> +#include <qregexp.h> + +#include <kglobal.h> + +#include "pilotDateEntry.h" + +static const char *default_date_category_names[] = { + "Unfiled", + "Business", + "Personal", + 0L +} ; + +void PilotDateInfo::resetToDefault() +{ + FUNCTIONSETUP; + // Reset to all 0s + memset(&fInfo,0,sizeof(fInfo)); + // Fill up default categories + for (unsigned int i=0; (i<4) && default_date_category_names[i]; ++i) + { + strncpy(fInfo.category.name[i],default_date_category_names[i],sizeof(fInfo.category.name[0])); + } + + fInfo.startOfWeek = 0; + +} + + +PilotDateEntry::PilotDateEntry():PilotRecordBase() +{ + ::memset(&fAppointmentInfo, 0, sizeof(struct Appointment)); +} + +/* initialize the entry from another one. If rec==NULL, this constructor does the same as PilotDateEntry() +*/ +PilotDateEntry::PilotDateEntry(PilotRecord * rec) : + PilotRecordBase(rec) +{ + ::memset(&fAppointmentInfo, 0, sizeof(fAppointmentInfo)); + if (rec) + { + // Construct a fake pi_buffer for unpack_Appointment. + // No ownership changes occur here. + pi_buffer_t b = { (unsigned char *) rec->data(), rec->size(), rec->size() } ; + unpack_Appointment(&fAppointmentInfo, &b, datebook_v1); + } + return; + +} + +void PilotDateEntry::_copyExceptions(const PilotDateEntry & e) +{ + if (e.fAppointmentInfo.exceptions > 0) + { + size_t blocksize = e.fAppointmentInfo.exceptions * + sizeof(struct tm); + + fAppointmentInfo.exception = (struct tm *)::malloc(blocksize); + + if (fAppointmentInfo.exception) + { + fAppointmentInfo.exceptions = + e.fAppointmentInfo.exceptions; + ::memcpy(fAppointmentInfo.exception, + e.fAppointmentInfo.exception, blocksize); + } + else + { + WARNINGKPILOT << "malloc() failed, exceptions not copied" << endl; + fAppointmentInfo.exceptions = 0; + } + } + else + { + fAppointmentInfo.exceptions = 0; + fAppointmentInfo.exception = 0L; + } +} + + +PilotDateEntry::PilotDateEntry(const PilotDateEntry & e) : + PilotRecordBase(e) +{ + ::memcpy(&fAppointmentInfo, &e.fAppointmentInfo, + sizeof(struct Appointment)); + // See operator = for explanation + fAppointmentInfo.exception = 0L; + fAppointmentInfo.description = 0L; + fAppointmentInfo.note = 0L; + + _copyExceptions(e); + setDescriptionP(e.fAppointmentInfo.description); + setNoteP(e.fAppointmentInfo.note); +} + + +PilotDateEntry & PilotDateEntry::operator = (const PilotDateEntry & e) +{ + if (this != &e) // Pointer equality! + { + KPILOT_FREE(fAppointmentInfo.exception); + KPILOT_FREE(fAppointmentInfo.description); + KPILOT_FREE(fAppointmentInfo.note); + ::memcpy(&fAppointmentInfo, &e.fAppointmentInfo, + sizeof(fAppointmentInfo)); + + // The original pointers were already freed; since we're now + // got the pointers from the new structure and we're going + // to use the standard set functions make sure that + // we don't free() the copies-of-pointers from e, which + // would be disastrous. + // + // + fAppointmentInfo.exception = 0L; + fAppointmentInfo.description = 0L; + fAppointmentInfo.note = 0L; + + _copyExceptions(e); + setDescriptionP(e.fAppointmentInfo.description); + setNoteP(e.fAppointmentInfo.note); + } + + return *this; +} // end of assignment operator + + +QString PilotDateEntry::getTextRepresentation(Qt::TextFormat richText) +{ + QString text, tmp; + QString par = (richText==Qt::RichText) ?CSL1("<p>"):QString::null; + QString ps = (richText==Qt::RichText) ?CSL1("</p>"):CSL1("\n"); + QString br = (richText==Qt::RichText) ?CSL1("<br/>"):CSL1("\n"); + + // title + name + text += par; + tmp=richText?CSL1("<b><big>%1</big></b>"):CSL1("%1"); + text += tmp.arg(rtExpand(getDescription(), richText)); + text += ps; + + QDateTime dt(readTm(getEventStart())); + QString startDate(dt.toString(Qt::LocalDate)); + text+=par; + text+=i18n("Start date: %1").arg(startDate); + text+=ps; + + if (isEvent()) + { + text+=par; + text+=i18n("Whole-day event"); + text+=ps; + } + else + { + dt=readTm(getEventEnd()); + QString endDate(dt.toString(Qt::LocalDate)); + text+=par; + text+=i18n("End date: %1").arg(endDate); + text+=ps; + } + + if ( isAlarmEnabled() ) + { + text+=par; + tmp=i18n("%1 is the duration, %2 is the time unit", "Alarm: %1 %2 before event starts"). + arg(getAdvance()); + switch (getAdvanceUnits()) + { + case advMinutes: tmp=tmp.arg(i18n("minutes")); break; + case advHours: tmp=tmp.arg(i18n("hours")); break; + case advDays: tmp=tmp.arg(i18n("days")); break; + default: tmp=tmp.arg(QString::null); break;; + } + text+=tmp; + text+=ps; + } + + if (getRepeatType() != repeatNone) + { + text+=par; + tmp=i18n("Recurrence: every %1 %2"); + int freq = getRepeatFrequency(); + tmp=tmp.arg(freq); + + switch(getRepeatType()) + { + case repeatDaily: tmp=tmp.arg(i18n("day(s)")); break; + case repeatWeekly: tmp=tmp.arg(i18n("week(s)")); break; + case repeatMonthlyByDay: + case repeatMonthlyByDate: tmp=tmp.arg(i18n("month(s)")); break; + case repeatYearly: tmp=tmp.arg(i18n("year(s)")); break; + default: tmp=tmp.arg(QString::null); break; + } + text+=tmp; + text+=br; + + bool repeatsForever = getRepeatForever(); + if (repeatsForever) + { + text+=i18n("Repeats indefinitely"); + } + else + { + dt = readTm(getRepeatEnd()).date(); + text+=i18n("Until %1").arg(dt.toString(Qt::LocalDate)); + } + text+=br; + + if (getRepeatType()==repeatMonthlyByDay) text+=i18n("Repeating on the i-th day of week j")+br; + if (getRepeatType()==repeatMonthlyByDate) text+=i18n("Repeating on the n-th day of the month")+br; + // TODO: show the dayArray when repeating weekly + /*QBitArray dayArray(7); + if (getRepeatType()==repeatWeekly) text+=i18n("Repeat day flags: %1").arg(getRepeatDays + const int *days = dateEntry->getRepeatDays(); + // Rotate the days of the week, since day numbers on the Pilot and + // in vCal / Events are different. + if (days[0]) dayArray.setBit(6); + for (int i = 1; i < 7; i++) + { + if (days[i]) dayArray.setBit(i-1); + }*/ + text+=ps; + } + + if (getExceptionCount()>0 ) + { + text+=par; + text+=i18n("Exceptions:")+br; + for (int i = 0; i < getExceptionCount(); i++) + { + QDate exdt=readTm(getExceptions()[i]).date(); + text+=exdt.toString(Qt::LocalDate); + text+=br; + } + text+=ps; + } + + if (!getNote().isEmpty()) + { + text += richText?CSL1("<hr/>"):CSL1("-------------------------\n"); + text+=par; + text+=richText?i18n("<b><em>Note:</em></b><br>"):i18n("Note:\n"); + text+=rtExpand(getNote(), richText); + text+=ps; + } + + return text; +} + +QDateTime PilotDateEntry::dtStart() const +{ + FUNCTIONSETUP; + return readTm( getEventStart() ); +} + +QDateTime PilotDateEntry::dtEnd() const +{ + FUNCTIONSETUP; + return readTm( getEventEnd() ); +} + +QDateTime PilotDateEntry::dtRepeatEnd() const +{ + FUNCTIONSETUP; + return readTm( getRepeatEnd() ); +} + +unsigned int PilotDateEntry::alarmLeadTime() const +{ + FUNCTIONSETUP; + if (!isAlarmEnabled()) return 0; + + int adv = getAdvance(); + if ( adv < 0 ) + { + return 0; // Not possible to enter on the pilot + } + unsigned int t = adv; + int u = getAdvanceUnits(); + + + switch(u) + { + case advMinutes : t *= 60; break; + case advHours : t *= 3600; break; + case advDays : t *= 3600 * 24; break; + default: t = 0; + } + + return t; +} + +PilotRecord *PilotDateEntry::pack() const +{ + int i; + + pi_buffer_t *b = pi_buffer_new( sizeof(fAppointmentInfo) ); + i = pack_Appointment(const_cast<Appointment_t *>(&fAppointmentInfo), b, datebook_v1); + if (i<0) + { + // Generic error from the pack_*() functions. + return 0; + } + + // pack_Appointment sets b->used + return new PilotRecord( b, this ); +} + +/* setExceptions sets a new set of exceptions. Note that + PilotDateEntry assumes ownership of the array and will + delete the old one. */ +void PilotDateEntry::setExceptions(struct tm *e) { + if (fAppointmentInfo.exception != e) + { + KPILOT_FREE(fAppointmentInfo.exception); + } + fAppointmentInfo.exception=e; +} + + +void PilotDateEntry::setDescriptionP(const char *desc, int l) +{ + FUNCTIONSETUP; + KPILOT_FREE(fAppointmentInfo.description); + + if (desc && *desc) + { + if (-1 == l) l=::strlen(desc); + fAppointmentInfo.description = + (char *) ::malloc(l + 1); + if (fAppointmentInfo.description) + { + strlcpy(fAppointmentInfo.description, desc, l+1); + } + else + { + WARNINGKPILOT << "malloc() failed, description not set" << endl; + } + } + else + { + fAppointmentInfo.description = 0L; + } +} + +void PilotDateEntry::setNoteP(const char *note, int l) +{ + FUNCTIONSETUP; + KPILOT_FREE(fAppointmentInfo.note); + + if (note && *note) + { + if (-1 == l) l=::strlen(note); + fAppointmentInfo.note = (char *)::malloc(l + 1); + if (fAppointmentInfo.note) + { + strlcpy(fAppointmentInfo.note, note,l+1); + } + else + { + WARNINGKPILOT << "malloc() failed, note not set" << endl; + } + } + else + { + fAppointmentInfo.note = 0L; + } +} + +void PilotDateEntry::setNote(const QString &s) +{ + QCString t = Pilot::toPilot(s); + setNoteP( t.data(),t.length() ); +} + +void PilotDateEntry::setLocation(const QString &s) +{ + QString note = Pilot::fromPilot(getNoteP()); + QRegExp rxp = QRegExp("^[Ll]ocation:[^\n]+\n"); + + // per QString docs, this covers null and 0 length + if( s.isEmpty() ) + { + note.replace(rxp,""); + } + else + { + QString location = "Location: " + s + "\n"; + int pos = note.find(rxp); + + if(pos >= 0) + { + note.replace( rxp, location ); + } + else + { + note = location + note; + setNote( note ); + } + } +} + +QString PilotDateEntry::getLocation() const +{ + // Read the complete note here and not the filtered + // one from PilotDateEntry::getNote(); + QString note = Pilot::fromPilot(getNoteP()); + QRegExp rxp = QRegExp("^[Ll]ocation:[^\n]+\n"); + int pos = note.find(rxp, 0); + + if(pos >= 0) + { + QString location = rxp.capturedTexts().first(); + rxp = QRegExp("^[Ll]ocation:[\\s|\t]*"); + location.replace(rxp,""); + location.replace("\n", ""); + return location; + } + else + { + return ""; + } +} + +void PilotDateEntry::setDescription(const QString &s) +{ + QCString t = Pilot::toPilot(s); + setDescriptionP( t.data(),t.length() ); +} + +QString PilotDateEntry::getNote() const +{ + QString note = Pilot::fromPilot(getNoteP()); + QRegExp rxp = QRegExp("^[Ll]ocation:[^\n]+\n"); + note.replace(rxp, "" ); + return note; +} + +QString PilotDateEntry::getDescription() const +{ + return Pilot::fromPilot(getDescriptionP()); +} + diff --git a/kpilot/lib/pilotDateEntry.h b/kpilot/lib/pilotDateEntry.h new file mode 100644 index 000000000..d9d5db2a6 --- /dev/null +++ b/kpilot/lib/pilotDateEntry.h @@ -0,0 +1,388 @@ +#ifndef _KPILOT_PILOTDATEENTRY_H +#define _KPILOT_PILOTDATEENTRY_H +/* pilotDateEntry.h -*- C++ -*- KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** See the .cc file for an explanation of what this file is for. +*/ + +/** @file pilotDateEntry.h defines a wrapper for datebook entries. */ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <pi-macros.h> +#include <pi-datebook.h> + +#include <qbitarray.h> +#include <qdatetime.h> +#include <qnamespace.h> + +#include "pilotRecord.h" +#include "pilotAppInfo.h" + +namespace KCal +{ +class Event; +} + +/** Interpreted form of the AppInfo block in the datebook database. */ +typedef PilotAppInfo< + AppointmentAppInfo, + unpack_AppointmentAppInfo, + pack_AppointmentAppInfo> PilotDateInfo_; + + +class PilotDateInfo : public PilotDateInfo_ +{ +public: + PilotDateInfo(PilotDatabase *d) : PilotDateInfo_(d) + { + } + + /** This resets the entire AppInfo block to one as it would be + * in an English-language handheld, with 3 categories and + * default field labels for everything. + */ + void resetToDefault(); + +}; + +/** This class is a wrapper for pilot-link's datebook entries (struct Appointment). */ +class KDE_EXPORT PilotDateEntry : public PilotRecordBase +{ +public: + /** Constructor. Zeroes out the appointment. */ + PilotDateEntry(); + + /** Constructor. Interprets the given record as an appointment. */ + PilotDateEntry(PilotRecord *rec); + + /** Copy constructor. */ + PilotDateEntry(const PilotDateEntry &e); + + /** Destructor. */ + ~PilotDateEntry() + { + free_Appointment(&fAppointmentInfo); + } + + /** Assignment operator. */ + PilotDateEntry& operator=(const PilotDateEntry &e); + + /** Create a textual representation (human-readable) of this appointment. + * If @p richText is true, then the text representation uses qt style + * tags as well. + */ + QString getTextRepresentation(Qt::TextFormat richText); + + /** Is this appointment a "floating" appointment? + * + * Floating appointments are those that have a day assigned, but no time + * in that day (birthday appointments are like that). You can think of these + * as "events", which don't have a time associated with them for a given day, + * as opposed to a regular "appointment", which does normally have a time + * associated with it. + */ + inline bool doesFloat() const + { + return fAppointmentInfo.event; + } + + /** Is this a non-time-related event as opposed to an appointment that has a + * time associated with it?. + */ + inline bool isEvent() const + { + return doesFloat(); + } + + /** Sets this appointment's floating status. + * + * Floating appointments are those that have a day assigned, but no time + * in that day (birthday appointments are like that). You can think of these + * as "events", which don't have a time associated with them for a given day, + * as opposed to a regular "appointment", which does normally have a time + * associated with it. + */ + inline void setFloats(bool f) + { + fAppointmentInfo.event = (f ? 1 : 0) /* Force 1 or 0 */ ; + } + + /** Get the start time of this appointment. See dtStart() for caveats. */ + inline struct tm getEventStart() const { return fAppointmentInfo.begin; } + + /** Get a pointer to the start time of this appointment. See dtStart() for caveats. */ + inline const struct tm *getEventStart_p() const + { + return &fAppointmentInfo.begin; + } + + /** Sets the start time of this appointment. */ + inline void setEventStart(struct tm& start) + { + fAppointmentInfo.begin = start; + } + + /** Get the start time of this appointment. For floating appointments, the + * time is undefined (perhaps 1 minute past midnight). + * + * Floating appointments are those that have a day assigned, but no time + * in that day (birthday appointments are like that). + */ + QDateTime dtStart() const; + + /** Get the end time of this appointment. See dtEnd() for caveats. */ + inline struct tm getEventEnd() const + { + return fAppointmentInfo.end; + } + + /** Get a pointer to the end time of this appointment. See dtEnd() for caveats. */ + inline const struct tm *getEventEnd_p() const + { + return &fAppointmentInfo.end; + } + + /** Set the end time of this appointment. */ + inline void setEventEnd(struct tm& end) + { + fAppointmentInfo.end = end; + } + + /** Get the end time of this appointment. For floating appointments, the + * time is undefined (perhaps 1 minute past midnight). + * + * Floating appointments are those that have a day assigned, but no time + * in that day (birthday appointments are like that). + */ + QDateTime dtEnd() const; + + /** Does this appointment have an alarm set? On the Pilot, an event + * may have an alarm (or not). If it has one, it is also enabled and + * causes the Pilot to beep (or whatever is set in the system preferences). + */ + inline bool isAlarmEnabled() const + { + return fAppointmentInfo.alarm; + } + + /** Set whether this appointment has an alarm. */ + inline void setAlarmEnabled(bool b) + { + fAppointmentInfo.alarm = (b?1:0) /* Force to known int values */ ; + } + + /** Get the numeric part of "alarm: __ (v) minutes" on the pilot -- you + * set the alarm time in two parts, a number and a unit type to use; unit + * types are minutes, hours, days and the number is whatever you like. + * + * If alarms are not enabled for this appointment, returns garbage. + * + * @see alarmLeadTime() + * @see dtAlarm() + */ + inline int getAdvance() const + { + return fAppointmentInfo.advance; + } + + /** Set the numeric part of the alarm setting. See getAdvance for details. */ + inline void setAdvance(int advance) + { + fAppointmentInfo.advance = advance; + } + + /** Returns the units part of the alarm time. See getAdvance . */ + inline int getAdvanceUnits() const + { + return fAppointmentInfo.advanceUnits; + } + + /** Sets the unites part of the alarm time. See getAdvance . */ + inline void setAdvanceUnits(int units) + { + fAppointmentInfo.advanceUnits = units; + } + + /** Returns the number of @em seconds "lead time" the alarm should sound + * before the actual appointment. This interprets the advance number and units. + * The value is always positive, 0 if no alarms are enabled. + */ + unsigned int alarmLeadTime() const; + + /** Returns the absolute date and time that the alarm should sound for + * this appointment. + */ + inline QDateTime dtAlarm() const + { + return dtStart().addSecs(-alarmLeadTime()); + } + + // The following need set routines written + inline repeatTypes getRepeatType() const + { + return fAppointmentInfo.repeatType; + } + inline void setRepeatType(repeatTypes r) + { + fAppointmentInfo.repeatType = r; + } + + inline int getRepeatForever() const + { + return fAppointmentInfo.repeatForever; + } + inline void setRepeatForever(int f = 1) + { + fAppointmentInfo.repeatForever = f; + } + + inline struct tm getRepeatEnd() const + { + return fAppointmentInfo.repeatEnd; + } + inline void setRepeatEnd(struct tm tm) + { + fAppointmentInfo.repeatEnd = tm; + } + + /** Returns the date and time that the repeat ends. If there is no repeat, + * returns an invalid date and time. + */ + QDateTime dtRepeatEnd() const; + + inline int getRepeatFrequency() const + { + return fAppointmentInfo.repeatFrequency; + } + inline void setRepeatFrequency(int f) + { + fAppointmentInfo.repeatFrequency = f; + } + + inline DayOfMonthType getRepeatDay() const + { + return fAppointmentInfo.repeatDay; + } + inline void setRepeatDay(DayOfMonthType rd) + { + fAppointmentInfo.repeatDay = rd; + }; + + inline const int *getRepeatDays() const + { + return fAppointmentInfo.repeatDays; + } + inline void setRepeatDays(int *rd) + { + for (int i = 0; i < 7; i++) + { + fAppointmentInfo.repeatDays[i] = rd[i]; + } + } + inline void setRepeatDays(QBitArray rba) + { + for (int i = 0; i < 7; i++) + { + fAppointmentInfo.repeatDays[i] = (rba[i] ? 1 : 0); + } + } + + inline int getExceptionCount() const + { + return fAppointmentInfo.exceptions; + } + inline void setExceptionCount(int e) + { + fAppointmentInfo.exceptions = e; + } + + inline const struct tm *getExceptions() const + { + return fAppointmentInfo.exception; + } + void setExceptions(struct tm *e); + + /** Sets the description of the appointment. This is the short string + * entered in the day view on the handheld, and it is called the summary + * in libkcal. + */ + void setDescription(const QString &); + /** Gets the description of the appointment. See setDescription for meaning. */ + QString getDescription() const; + + /** Sets the note for the appointment. The note is the long text entry + * that is possible - but clumsy - on the handheld. It is called the + * description in libkcal. + */ + void setNote(const QString &); + /** Gets the note for this appointment. See setNote for meaning. */ + QString getNote() const; + + /** + * Sets the location for the appointment. For now it will be placed within + * the notes on the handheld. It will be placed on one line and starts with: + * Location: {location}. Everything on that line will be counted as location. + * TODO: Make distinguish between handhelds that support the location field + * and the ones that don't. (Shouldn't this be done in the pilot-link lib?) + */ + void setLocation(const QString &); + + /** Gets the location for this appointment. See setNote for meaning. */ + QString getLocation() const; + +protected: + void setDescriptionP(const char* desc, int l=-1); + const char* getDescriptionP() const + { + return fAppointmentInfo.description; + } + + void setNoteP(const char* note, int l=-1); + const char* getNoteP() const + { + return fAppointmentInfo.note; + } + +public: + bool isMultiDay() const + { + return ((fAppointmentInfo.repeatType == repeatDaily) && + (fAppointmentInfo.repeatFrequency == 1) && + ( !getRepeatForever() ) && + !doesFloat() ); + } + + PilotRecord *pack() const; + +private: + struct Appointment fAppointmentInfo; + void _copyExceptions(const PilotDateEntry &e); +}; + + + +#endif + diff --git a/kpilot/lib/pilotLinkVersion.h b/kpilot/lib/pilotLinkVersion.h new file mode 100644 index 000000000..1255baade --- /dev/null +++ b/kpilot/lib/pilotLinkVersion.h @@ -0,0 +1,60 @@ +#ifndef _KPILOT_PILOTLINKVERSION_H +#define _KPILOT_PILOTLINKVERSION_H +/* KPilot +** +** Copyright (C) 2005 by Adriaan de Groot +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include <pi-version.h> + +/** @file +* Checks the pilot-link version and defines some convenience macros. +* The main point of this file is to complain if you have a version +* of pilot-link before 0.12, which no longer work with KPilot. +*/ + +#ifndef PILOT_LINK_VERSION +#error "You need at least pilot-link version 0.12.0" +#endif + + +#define PILOT_LINK_NUMBER ((10000*PILOT_LINK_VERSION) + \ + (100*PILOT_LINK_MAJOR)+PILOT_LINK_MINOR) +#define PILOT_LINK_0_10_0 (1000) +#define PILOT_LINK_0_11_0 (1100) +#define PILOT_LINK_0_11_8 (1108) +#define PILOT_LINK_0_12_0 (1200) +#define PILOT_LINK_0_12_1 (1201) + +#if PILOT_LINK_NUMBER < PILOT_LINK_0_12_0 +#error "You need at least pilot-link version 0.12.0 for KPilot" +#endif + +#define PI_SIZE_T size_t + + +#endif + diff --git a/kpilot/lib/pilotLocalDatabase.cc b/kpilot/lib/pilotLocalDatabase.cc new file mode 100644 index 000000000..9f2d5e8a4 --- /dev/null +++ b/kpilot/lib/pilotLocalDatabase.cc @@ -0,0 +1,762 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This defines an interface to Pilot databases on the local disk. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "options.h" + +#include <stdio.h> +#include <unistd.h> +#include <assert.h> + +#include <iostream> + +#include <pi-file.h> + +#include <qstring.h> +#include <qfile.h> +#include <qregexp.h> +#include <qdatetime.h> +#include <qvaluevector.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <ksavefile.h> + +#include "pilotRecord.h" +#include "pilotLocalDatabase.h" + +typedef QValueVector<PilotRecord *> Records; + +class PilotLocalDatabase::Private : public Records +{ +public: + static const int DEFAULT_SIZE = 128; + Private(int size=DEFAULT_SIZE) : Records(size) { resetIndex(); } + ~Private() { deleteRecords(); } + + void deleteRecords() + { + for (unsigned int i=0; i<size(); i++) + { + delete at(i); + } + clear(); + resetIndex(); + } + + void resetIndex() + { + current = 0; + pending = -1; + } + + unsigned int current; + int pending; +} ; + +PilotLocalDatabase::PilotLocalDatabase(const QString & path, + const QString & dbName, bool useDefaultPath) : + PilotDatabase(dbName), + fPathName(path), + fDBName(dbName), + fAppInfo(0L), + fAppLen(0), + d(0L) +{ + FUNCTIONSETUP; + fixupDBName(); + openDatabase(); + + if (!isOpen() && useDefaultPath) + { + if (fPathBase && !fPathBase->isEmpty()) + { + fPathName = *fPathBase; + } + else + { + fPathName = KGlobal::dirs()->saveLocation("data", + CSL1("kpilot/DBBackup/")); + } + fixupDBName(); + openDatabase(); + if (!isOpen()) + { + fPathName=path; + } + } + +} + +PilotLocalDatabase::PilotLocalDatabase(const QString &dbName) : + PilotDatabase( QString() ), + fPathName( QString() ), + fDBName( QString() ), + fAppInfo(0L), + fAppLen(0), + d(0L) +{ + FUNCTIONSETUP; + + int p = dbName.findRev( '/' ); + if (p<0) + { + // No slash + fPathName = CSL1("."); + fDBName = dbName; + } + else + { + fPathName = dbName.left(p); + fDBName = dbName.mid(p+1); + } + openDatabase(); +} + +PilotLocalDatabase::~PilotLocalDatabase() +{ + FUNCTIONSETUP; + + closeDatabase(); + delete[]fAppInfo; + delete d; +} + +// Changes any forward slashes to underscores +void PilotLocalDatabase::fixupDBName() +{ + FUNCTIONSETUP; + fDBName = fDBName.replace(CSL1("/"),CSL1("_")); +} + +bool PilotLocalDatabase::createDatabase(long creator, long type, int, int flags, int version) +{ + FUNCTIONSETUP; + + // if the database is already open, we cannot create it again. + // How about completely resetting it? (i.e. deleting it and then + // creating it again) + if (isOpen()) + { + DEBUGKPILOT << fname << ": Database " << fDBName + << " already open. Cannot recreate it." << endl; + return true; + } + + DEBUGKPILOT << fname << ": Creating database " << fDBName << endl; + + // Database names seem to be latin1. + Pilot::toPilot(fDBName, fDBInfo.name, sizeof(fDBInfo.name)); + fDBInfo.creator=creator; + fDBInfo.type=type; + fDBInfo.more=0; + fDBInfo.flags=flags; + fDBInfo.miscFlags=0; + fDBInfo.version=version; + fDBInfo.modnum=0; + fDBInfo.index=0; + fDBInfo.createDate=(QDateTime::currentDateTime()).toTime_t(); + fDBInfo.modifyDate=(QDateTime::currentDateTime()).toTime_t(); + fDBInfo.backupDate=(QDateTime::currentDateTime()).toTime_t(); + + delete[] fAppInfo; + fAppInfo=0L; + fAppLen=0; + + d = new Private; + + // TODO: Do I have to open it explicitly??? + setDBOpen(true); + return true; +} + +int PilotLocalDatabase::deleteDatabase() +{ + FUNCTIONSETUP; + if (isOpen()) + { + closeDatabase(); + } + + QString dbpath=dbPathName(); + QFile fl(dbpath); + if (QFile::remove(dbPathName())) + { + return 0; + } + else + { + return -1; + } +} + + + +// Reads the application block info +int PilotLocalDatabase::readAppBlock(unsigned char *buffer, int size) +{ + FUNCTIONSETUP; + + size_t m = kMin((size_t)size,(size_t)fAppLen); + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + memset(buffer,0,m); + return -1; + } + + memcpy((void *) buffer, fAppInfo, m); + return fAppLen; +} + +int PilotLocalDatabase::writeAppBlock(unsigned char *buffer, int len) +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return -1; + } + delete[]fAppInfo; + fAppLen = len; + fAppInfo = new char[fAppLen]; + + memcpy(fAppInfo, (void *) buffer, fAppLen); + return 0; +} + + +// returns the number of records in the database +unsigned int PilotLocalDatabase::recordCount() const +{ + if (d && isOpen()) + { + return d->size(); + } + else + { + return 0; + } +} + + +// Returns a QValueList of all record ids in the database. +QValueList<recordid_t> PilotLocalDatabase::idList() +{ + int idlen=recordCount(); + QValueList<recordid_t> idlist; + if (idlen<=0) + { + return idlist; + } + + // now create the QValue list from the idarr: + for (int i=0; i<idlen; i++) + { + idlist.append((*d)[i]->id()); + } + + return idlist; +} + +// Reads a record from database by id, returns record length +PilotRecord *PilotLocalDatabase::readRecordById(recordid_t id) +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "Database '" << fDBName << " not open!" << endl; + return 0L; + } + + d->pending = -1; + + for (unsigned int i = 0; i < d->size(); i++) + { + if ((*d)[i]->id() == id) + { + PilotRecord *newRecord = new PilotRecord((*d)[i]); + d->current = i; + return newRecord; + } + } + return 0L; +} + +// Reads a record from database, returns the record +PilotRecord *PilotLocalDatabase::readRecordByIndex(int index) +{ + FUNCTIONSETUP; + + if (index < 0) + { + DEBUGKPILOT << fname << ": Index " << index << " is bogus." << endl; + return 0L; + } + + d->pending = -1; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0L; + } + + DEBUGKPILOT << fname << ": Index=" << index << " Count=" << recordCount() << endl; + + if ( (unsigned int)index >= recordCount() ) + { + return 0L; + } + PilotRecord *newRecord = new PilotRecord((*d)[index]); + d->current = index; + + return newRecord; +} + +// Reads the next record from database in category 'category' +PilotRecord *PilotLocalDatabase::readNextRecInCategory(int category) +{ + FUNCTIONSETUP; + d->pending = -1; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0L; + } + + while ((d->current < d->size()) + && ((*d)[d->current]->category() != category)) + { + d->current++; + } + + if (d->current >= d->size()) + return 0L; + PilotRecord *newRecord = new PilotRecord((*d)[d->current]); + + d->current++; // so we skip it next time + return newRecord; +} + +const PilotRecord *PilotLocalDatabase::findNextNewRecord() +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0L; + } + DEBUGKPILOT << fname << ": looking for new record from " << d->current << endl; + // Should this also check for deleted? + while ((d->current < d->size()) + && ((*d)[d->current]->id() != 0 )) + { + d->current++; + } + + if (d->current >= d->size()) + return 0L; + + d->pending = d->current; // Record which one needs the new id + d->current++; // so we skip it next time + return (*d)[d->pending]; +} + +PilotRecord *PilotLocalDatabase::readNextModifiedRec(int *ind) +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0L; + } + + d->pending = -1; + // Should this also check for deleted? + while ((d->current < d->size()) + && !((*d)[d->current]->isModified()) && ((*d)[d->current]->id()>0 )) + { + d->current++; + } + + if (d->current >= d->size()) + { + return 0L; + } + PilotRecord *newRecord = new PilotRecord((*d)[d->current]); + if (ind) + { + *ind=d->current; + } + + d->pending = d->current; // Record which one needs the new id + d->current++; // so we skip it next time + return newRecord; +} + +// Writes a new ID to the record specified the index. Not supported on Serial connections +recordid_t PilotLocalDatabase::updateID(recordid_t id) +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0; + } + if (d->pending < 0) + { + WARNINGKPILOT << "Last call was NOT readNextModifiedRec()" << endl; + return 0; + } + (*d)[d->pending]->setID(id); + d->pending = -1; + return id; +} + +// Writes a new record to database (if 'id' == 0, it is assumed that this is a new record to be installed on pilot) +recordid_t PilotLocalDatabase::writeRecord(PilotRecord * newRecord) +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return 0; + } + + d->pending = -1; + if (!newRecord) + { + WARNINGKPILOT << "Record to be written is invalid!" << endl; + return 0; + } + + // Instead of making the app do it, assume that whenever a record is + // written to the database it is dirty. (You can clean up the database with + // resetSyncFlags().) This will make things get copied twice during a hot-sync + // but shouldn't cause any other major headaches. + newRecord->setModified( true ); + + // First check to see if we have this record: + if (newRecord->id() != 0) + { + for (unsigned int i = 0; i < d->size(); i++) + if ((*d)[i]->id() == newRecord->id()) + { + delete (*d)[i]; + + (*d)[i] = new PilotRecord(newRecord); + return 0; + } + } + // Ok, we don't have it, so just tack it on. + d->append( new PilotRecord(newRecord) ); + return newRecord->id(); +} + +// Deletes a record with the given recordid_t from the database, or all records, if all is set to true. The recordid_t will be ignored in this case +int PilotLocalDatabase::deleteRecord(recordid_t id, bool all) +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT <<"DB not open"<<endl; + return -1; + } + d->resetIndex(); + if (all) + { + d->deleteRecords(); + d->clear(); + return 0; + } + else + { + Private::Iterator i; + for ( i=d->begin() ; i!=d->end(); ++i) + { + if ((*i) && (*i)->id() == id) break; + } + if ( (i!=d->end()) && (*i) && (*i)->id() == id) + { + d->erase(i); + } + else + { + // Record with this id does not exist! + return -1; + } + } + return 0; +} + + +// Resets all records in the database to not dirty. +int PilotLocalDatabase::resetSyncFlags() +{ + FUNCTIONSETUP; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return -1; + } + d->pending = -1; + for (unsigned int i = 0; i < d->size(); i++) + { + (*d)[i]->setModified( false ); + } + return 0; +} + +// Resets next record index to beginning +int PilotLocalDatabase::resetDBIndex() +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return -1; + } + d->resetIndex(); + return 0; +} + +// Purges all Archived/Deleted records from Palm Pilot database +int PilotLocalDatabase::cleanup() +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open!" << endl; + return -1; + } + d->resetIndex(); + + /* Not the for loop one might expect since when we erase() + * a record the iterator changes too. + */ + Private::Iterator i = d->begin(); + while ( i!=d->end() ) + { + if ( (*i)->isDeleted() || (*i)->isArchived() ) + { + delete (*i); + i = d->erase(i); + } + else + { + ++i; + } + } + + // Don't have to do anything. Will be taken care of by closeDatabase()... + // Changed! + return 0; +} + +QString PilotLocalDatabase::dbPathName() const +{ + FUNCTIONSETUP; + QString tempName(fPathName); + QString slash = CSL1("/"); + + if (!tempName.endsWith(slash)) tempName += slash; + tempName += getDBName(); + tempName += CSL1(".pdb"); + return tempName; +} + +void PilotLocalDatabase::openDatabase() +{ + FUNCTIONSETUP; + + pi_file *dbFile; + + setDBOpen(false); + + dbFile = pi_file_open( QFile::encodeName(dbPathName()) ); + if (dbFile == 0L) + { + QString path = dbPathName(); + DEBUGKPILOT << fname << ": Failed to open " << path << endl; + return; + } + + + PI_SIZE_T size = 0; + void *tmpBuffer; + pi_file_get_info(dbFile, &fDBInfo); + pi_file_get_app_info(dbFile, &tmpBuffer, &size); + fAppLen = size; + fAppInfo = new char[fAppLen]; + memcpy(fAppInfo, tmpBuffer, fAppLen); + + int count; + pi_file_get_entries(dbFile, &count); + if (count >= 0) + { + KPILOT_DELETE(d); + d = new Private(count); + } + + int attr, cat; + recordid_t id; + unsigned int i = 0; + while (pi_file_read_record(dbFile, i, + &tmpBuffer, &size, &attr, &cat, &id) == 0) + { + pi_buffer_t *b = pi_buffer_new(size); + memcpy(b->data,tmpBuffer,size); + b->used = size; + (*d)[i] = new PilotRecord(b, attr, cat, id); + i++; + } + pi_file_close(dbFile); // We done with it once we've read it in. + + KSaveFile::backupFile( dbPathName() ); + + setDBOpen(true); +} + +void PilotLocalDatabase::closeDatabase() +{ + FUNCTIONSETUP; + pi_file *dbFile; + + if (!isOpen()) + { + DEBUGKPILOT << fname << ": Database " << fDBName + << " is not open. Cannot close and write it" + << endl; + return; + } + + QString newName = dbPathName() + CSL1(".new"); + QString path = dbPathName(); + DEBUGKPILOT << fname + << ": Creating temp file " << newName + << " for the database file " << path << endl; + + dbFile = pi_file_create(QFile::encodeName(newName),&fDBInfo); + pi_file_set_app_info(dbFile, fAppInfo, fAppLen); + + for (unsigned int i = 0; i < d->size(); i++) + { + // How did a NULL pointer sneak in here? + if (!(*d)[i]) + { + continue; + } + + if (((*d)[i]->id() == 0) && ((*d)[i]->isDeleted())) + { + // Just ignore it + } + else + { + pi_file_append_record(dbFile, + (*d)[i]->data(), + (*d)[i]->size(), + (*d)[i]->attributes(), (*d)[i]->category(), + (*d)[i]->id()); + } + } + + pi_file_close(dbFile); + QFile::remove(dbPathName()); + rename((const char *) QFile::encodeName(newName), + (const char *) QFile::encodeName(dbPathName())); + setDBOpen(false); +} + + +QString *PilotLocalDatabase::fPathBase = 0L; + +void PilotLocalDatabase::setDBPath(const QString &s) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname + << ": Setting default DB path to " + << s + << endl; + + if (!fPathBase) + { + fPathBase = new QString(s); + } + else + { + *fPathBase = s; + } +} + +/* virtual */ PilotDatabase::DBType PilotLocalDatabase::dbType() const +{ + return eLocalDB; +} + + +/* static */ bool PilotLocalDatabase::infoFromFile( const QString &path, DBInfo *d ) +{ + FUNCTIONSETUP; + + pi_file *f = 0L; + + if (!d) + { + return false; + } + if (!QFile::exists(path)) + { + return false; + } + + QCString fileName = QFile::encodeName( path ); + f = pi_file_open( fileName ); + if (!f) + { + WARNINGKPILOT << "Can't open " << path << endl; + return false; + } + + pi_file_get_info(f,d); + pi_file_close(f); + + return true; +} + diff --git a/kpilot/lib/pilotLocalDatabase.h b/kpilot/lib/pilotLocalDatabase.h new file mode 100644 index 000000000..c33455800 --- /dev/null +++ b/kpilot/lib/pilotLocalDatabase.h @@ -0,0 +1,201 @@ +#ifndef _KPILOT_PILOTLOCALDATABASE_H +#define _KPILOT_PILOTLOCALDATABASE_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "pilotDatabase.h" + +/** @file +* Defines the PilotLocalDatabase class, for databases stored +* on disk (as opposed to in a handheld). +*/ + +/** +* PilotLocalDatabase represents databases in the same binary format +* as on the handheld but which are stored on local disk. +*/ +class KDE_EXPORT PilotLocalDatabase : public PilotDatabase +{ +public: + /** + * Opens the local database. If the database cannot be found at the + * given position, a default path is used + * ($KDEHOME/share/apps/kpilot/DBBackup) + * and if the file is found there, it is opened. In some cases this should + * not be done, so the parameter useDefaultPath controls this behavior. + * If it is set to true, the default path is used if the file cannot be + * found in the explicitly given location. If it is set to false and + * the database cannot be found, no database is opened. It can then be + * created explicitly at the specified location. + */ + PilotLocalDatabase( const QString& path, + const QString& name, bool useDefaultPath=true); + + /** + * Opens the local database. This is primarily for testing + * purposes; only tries the given path. + */ + PilotLocalDatabase(const QString &name); + + virtual ~PilotLocalDatabase(); + + /** Creates the database with the given creator, type and flags on + * the given card (default is RAM). If the database already exists, + * this function does nothing. + */ + virtual bool createDatabase(long creator=0, + long type=0, int cardno=0, int flags=0, int version=0); + + + + /** Deletes the database (by name, as given in the constructor + * and stored in the fDBName field. ) + */ + virtual int deleteDatabase(); + + // Reads the application block info + virtual int readAppBlock(unsigned char* buffer, int maxLen); + // Writes the application block info. + virtual int writeAppBlock(unsigned char* buffer, int len); + // returns the number of records in the database, 0 if not open + virtual unsigned int recordCount() const; + // Returns a QValueList of all record ids in the database. + virtual QValueList<recordid_t> idList(); + // Reads a record from database by id, returns record + virtual PilotRecord* readRecordById(recordid_t id); + // Reads a record from database, returns the record + virtual PilotRecord* readRecordByIndex(int index); + // Reads the next record from database in category 'category' + virtual PilotRecord* readNextRecInCategory(int category); + /** + * Returns the next "new" record, ie. the next record + * that has not been synced yet. These records all have ID=0, so are + * not easy to find with the other methods. The record is the one + * contained in the database, not a copy like the read*() functions + * give you -- so be careful with it. Don't delete it, in any case. + * Casting it to non-const and marking it deleted is OK, though, + * which is mostly its intended use. + */ + const PilotRecord *findNextNewRecord(); + + /** + * Reads the next record from database that has the dirty flag set. + * ind (if a valid pointer is given) will receive the index of the + * returned record. + */ + virtual PilotRecord* readNextModifiedRec(int *ind=0L); + // Writes a new record to database (if 'id' == 0, none is assigned, either) + virtual recordid_t writeRecord(PilotRecord* newRecord); + /** + * Deletes a record with the given recordid_t from the database, + * or all records, if all is set to true. The recordid_t will be + * ignored in this case. Return value is negative on error, 0 otherwise. + */ + virtual int deleteRecord(recordid_t id, bool all=false); + // Resets all records in the database to not dirty. + virtual int resetSyncFlags(); + // Resets next record index to beginning + virtual int resetDBIndex(); + // Purges all Archived/Deleted records from Palm Pilot database + virtual int cleanup(); + + + /** Update the ID of the current record in the database with + * the specified @param id . This is allowed only after + * reading or writing a modified or new record. + */ + virtual recordid_t updateID(recordid_t id); + + + /** Return the name of the database (as it would be on the handheld). */ + QString getDBName() const { return fDBName; } + + /** + * Returns the full path of the current database, based on + * the path and dbname passed to the constructor, and including + * the .pdb extension. + */ + virtual QString dbPathName() const; + + /** + * Accessor functions for the application info block. + */ + int appInfoSize() const + { if (isOpen()) return fAppLen; else return -1; } ; + char *appInfo() { return fAppInfo; } ; + + const struct DBInfo &getDBInfo() const { return fDBInfo; } + void setDBInfo(const struct DBInfo &dbi) {fDBInfo=dbi; } + + virtual DBType dbType() const; + + /** Reads local file @p path and fills in the DBInfo + * structure @p d with the DBInfo from the file. + * + * @return @c false if d is NULL + * @return @c false if the file @p path does not exist + * @return @c true if reading the DBInfo succeeds + * + * @note Relatively expensive operation, since the pilot-link + * library doesn't provide a cheap way of getting this + * information. + */ + static bool infoFromFile( const QString &path, DBInfo *d ); + +protected: + // Changes any forward slashes to underscores + void fixupDBName(); + virtual void openDatabase(); + virtual void closeDatabase(); + +private: + struct DBInfo fDBInfo; + QString fPathName,fDBName; + char* fAppInfo; + size_t fAppLen; + + class Private; + Private *d; + +public: + /** + * For databases opened by name only (constructor 2 -- which is the + * preferred one, too) try this path first before the default path. + * Set statically so it's shared for all local databases. + */ + static void setDBPath(const QString &); + /** + * Accessor for the extra search path. + */ + static const QString &getDBPath() { return *fPathBase; } ; +private: + static QString *fPathBase; +}; + +#endif diff --git a/kpilot/lib/pilotMemo.cc b/kpilot/lib/pilotMemo.cc new file mode 100644 index 000000000..0ce09f8a7 --- /dev/null +++ b/kpilot/lib/pilotMemo.cc @@ -0,0 +1,135 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** +** This is a C++ wrapper for the Pilot's Memo Pad structures. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <qnamespace.h> + +#include "pilotMemo.h" +#include "pilotDatabase.h" + + + +PilotMemo::PilotMemo(const PilotRecord * rec) : PilotRecordBase(rec) +{ + FUNCTIONSETUP; + fText = Pilot::fromPilot((const char *)(rec->data()),rec->size()); +} + +PilotRecord *PilotMemo::pack() +{ + FUNCTIONSETUPL(4); + int i; + + int len = fText.length() + 8; + struct Memo buf; + buf.text = new char[len]; + + // put our text into buf + i = Pilot::toPilot(fText, buf.text, len); + + pi_buffer_t *b = pi_buffer_new(len); + i = pack_Memo(&buf, b, memo_v1); + + DEBUGKPILOT << fname << ": original text: [" << fText + << "], buf.text: [" << buf.text + << "], b->data: [" << b->data << "]" << endl; + + if (i<0) + { + // Generic error from the pack_*() functions. + delete[] buf.text; + return 0; + } + + // pack_Appointment sets b->used + PilotRecord *r = new PilotRecord(b, this); + delete[] buf.text; + return r; +} + + +QString PilotMemo::getTextRepresentation(Qt::TextFormat richText) +{ + if (richText==Qt::RichText) + { + return i18n("<i>Title:</i> %1<br>\n<i>MemoText:</i><br>%2"). + arg(rtExpand(getTitle(), richText)).arg(rtExpand(text(), richText)); + } + else + { + return i18n("Title: %1\nMemoText:\n%2").arg(getTitle()).arg(text()); + } +} + + +QString PilotMemo::getTitle() const +{ + if (fText.isEmpty()) return QString::null; + + int memoTitleLen = fText.find('\n'); + if (-1 == memoTitleLen) memoTitleLen=fText.length(); + return fText.left(memoTitleLen); +} + +QString PilotMemo::shortTitle() const +{ + FUNCTIONSETUP; + QString t = QString(getTitle()).simplifyWhiteSpace(); + + if (t.length() < 32) + return t; + t.truncate(40); + + int spaceIndex = t.findRev(' '); + + if (spaceIndex > 32) + { + t.truncate(spaceIndex); + } + + t += CSL1("..."); + + return t; +} + +QString PilotMemo::sensibleTitle() const +{ + FUNCTIONSETUP; + QString s = getTitle(); + + if (!s.isEmpty()) + { + return s; + } + else + { + return i18n("[unknown]"); + } +} + diff --git a/kpilot/lib/pilotMemo.h b/kpilot/lib/pilotMemo.h new file mode 100644 index 000000000..6897a0417 --- /dev/null +++ b/kpilot/lib/pilotMemo.h @@ -0,0 +1,105 @@ +#ifndef _KPILOT_PILOTMEMO_H +#define _KPILOT_PILOTMEMO_H +/* pilotMemo.h KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** +** See the .cc file for an explanation of what this file is for. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qnamespace.h> +#include <qstring.h> + +#include <pi-memo.h> + +#include "pilotRecord.h" +#include "pilotAppInfo.h" + +class KDE_EXPORT PilotMemo : public PilotRecordBase +{ +public: + /** + * Constructor. Create an empty memo. + */ + PilotMemo(void) : PilotRecordBase() { } ; + + /** + * Constructor. Create a memo in the Unfiled category with + * text @p s . + */ + PilotMemo(const QString &s) : PilotRecordBase() + { + setText(s); + } ; + + /** + * Constructor. Create a memo with the category and + * attributes of the given record @p rec, and extract + * the text from that record as if it comes from the MemoDB. + */ + PilotMemo(const PilotRecord* rec); + + /** + * Constructor. Create a memo with category and + * attributes from the argument @p r, and set the + * text of the memo from string @p s. + */ + PilotMemo(const PilotRecordBase *r, const QString &s) : + PilotRecordBase(r) + { + setText(s); + } + + ~PilotMemo() { } ; + + virtual QString getTextRepresentation(Qt::TextFormat richText); + QString text(void) const { return fText; } ; + void setText(const QString &text) { fText = text.left(MAX_MEMO_LEN); } ; + QString getTitle(void) const ; + PilotRecord* pack(); + + static const int MAX_MEMO_LEN=8192; + + /** + * Return a "short but sensible" title. getTitle() returns the + * first line of the memo, which may be very long + * and inconvenient. shortTitle() returns about 30 + * characters. + */ + QString shortTitle() const; + + /** + * Returns a (complete) title if there is one and [unknown] + * otherwise. + */ + QString sensibleTitle() const; + +private: + QString fText; + +}; + +typedef PilotAppInfo<struct MemoAppInfo,unpack_MemoAppInfo, pack_MemoAppInfo> PilotMemoInfo; + +#endif diff --git a/kpilot/lib/pilotRecord.cc b/kpilot/lib/pilotRecord.cc new file mode 100644 index 000000000..44ae4f7be --- /dev/null +++ b/kpilot/lib/pilotRecord.cc @@ -0,0 +1,132 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This is a wrapper for pilot-link's general +** Pilot database structures. These records are +*** just collections of bits. See PilotAppCategory +** for interpreting the bits in a meaningful way. +** +** As a crufty hack, the non-inline parts of +** PilotAppCategory live in this file as well. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ +#include "options.h" + +#include <string.h> + +#include <qregexp.h> + +#include <kglobal.h> +#include <kcharsets.h> + +#include "pilot.h" +#include "pilotRecord.h" + + + +/* virtual */ QString PilotRecordBase::textRepresentation() const +{ + return CSL1("[ %1,%2,%3 ]") . arg(attributes(),category(),id()); +} + +/* virtual */ QString PilotRecord::textRepresentation() const +{ + return CSL1("[ %1,%2 ]") + .arg(PilotRecordBase::textRepresentation()) + .arg(size()); +} + + + +/* static */ int PilotRecord::fAllocated = 0; +/* static */ int PilotRecord::fDeleted = 0; + +/* static */ void PilotRecord::allocationInfo() +{ + FUNCTIONSETUP; + DEBUGKPILOT << fname + << ": Allocated " << fAllocated + << " Deleted " << fDeleted << endl; +} + +PilotRecord::PilotRecord(void *data, int len, int attrib, int cat, recordid_t uid) : + PilotRecordBase(attrib,cat,uid), + fData(0L), + fLen(len), + fBuffer(0L) +{ + FUNCTIONSETUPL(4); + fData = new char[len]; + + memcpy(fData, data, len); + + fAllocated++; +} + +PilotRecord::PilotRecord(PilotRecord * orig) : + PilotRecordBase( orig->attributes(), orig->category(), orig->id() ) , + fBuffer(0L) +{ + FUNCTIONSETUPL(4); + fData = new char[orig->size()]; + + memcpy(fData, orig->data(), orig->size()); + fLen = orig->size(); + fAllocated++; +} + +PilotRecord & PilotRecord::operator = (PilotRecord & orig) +{ + FUNCTIONSETUP; + if (fBuffer) + { + pi_buffer_free(fBuffer); + fBuffer=0L; + fData=0L; + } + + if (fData) + delete[]fData; + fData = new char[orig.size()]; + + memcpy(fData, orig.data(), orig.size()); + fLen = orig.size(); + setAttributes( orig.attributes() ); + setCategory( orig.category() ); + setID( orig.id() ); + return *this; +} + +void PilotRecord::setData(const char *data, int len) +{ + FUNCTIONSETUP; + if (fData) + delete[]fData; + fData = new char[len]; + + memcpy(fData, data, len); + fLen = len; +} + diff --git a/kpilot/lib/pilotRecord.h b/kpilot/lib/pilotRecord.h new file mode 100644 index 000000000..a3812b333 --- /dev/null +++ b/kpilot/lib/pilotRecord.h @@ -0,0 +1,355 @@ +#ifndef _KPILOT_PILOTRECORD_H +#define _KPILOT_PILOTRECORD_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "pilot.h" + +/** +* @file This file defines the lowest- denominator representation(s) +*of the bits used in a Pilot-based database record. +*/ + + +/** +* All entries in the Handheld -- whether interpreted or binary blobs -- +* have some common characteristics, viz. an ID number, a category, +* and some attributes defined by the handheld. PilotRecordBase is +* a common base class collecting methods to manipulate those +* common characteristics. +*/ +class KDE_EXPORT PilotRecordBase +{ +public: + /** Constructor. Initialize the characteristics to the + * given values. + * + * @param attrib Attributes (bitfield) for this entry. + * @param cat Category for this entry. Should be in the + * range 0 <= cat < Pilot::CATEGORY_COUNT . Using an + * invalid category means 0 (unfiled) is used. + * @param id Unique ID for this entry. May be 0 (non-unique) as well. + */ + PilotRecordBase(int attrib=0, int cat=0, recordid_t id=0) : + fAttrib(attrib),fCat(0),fID(id) + { + setCategory(cat); + } + + /** Constructor. Initializes the characteristics from + * the values the given record @p b has. When @p b is + * NULL (which is allowed), everything is assumed zero. + * No ownership is transferred. + * + * @param b Record to take characteristics from. + */ + PilotRecordBase( const PilotRecordBase *b ) : + fAttrib( b ? b->attributes() : 0 ), + fCat( 0 ), + fID( b ? b->id() : 0 ) + { + if (b) + { + setCategory( b->category() ); + } + } + + /** Destructor. Nothing to do for it. */ + virtual ~PilotRecordBase() { } ; + + /** Attributes of this record (deleted, secret, ...); + * it's a bitfield. + */ + inline int attributes() const + { + return fAttrib; + } + + /** Set the attributes of this record. */ + inline void setAttributes(int attrib) + { + fAttrib = attrib; + } + + /** Returns the category number [ 0 .. Pilot::CATEGORY_COUNT-1] + * of this record. + */ + inline int category() const + { + return fCat; + } + + /** Sets the category number [ 0 .. Pilot::CATEGORY_COUNT-1] + * of this record. + * Trying to set an illegal category number files this one under + * "Unfiled" (which is 0). + */ + inline void setCategory(int cat) + { + if ( (cat<0) || (cat>=(int)Pilot::CATEGORY_COUNT)) + { + cat=0; + } + fCat = cat; + } + + /** Sets the category number by looking up the string @p label + * in the category table @p info . Leaves the category unchanged + * if no match is found and returns @c false. + * + * @param info AppInfo structure containing the labels (in handheld + * native encoding). + * @param label The label to look for. + * + * @return @c true on success, @c false on failure + */ + bool setCategory(const struct CategoryAppInfo *info, const QString &label) + { + if (!info) + { + return false; + } + + int cat = Pilot::findCategory( info, label, false ); + if ( (cat<0) || (cat>=(int)Pilot::CATEGORY_COUNT) ) + { + return false; + } + else + { + setCategory( cat ); + return true; + } + } + + /** Returns the record ID for this record. Record IDs are unique for a given + * handheld and database. + */ + inline recordid_t id() const + { + return fID; + } + + /** Sets the record ID for this record. Use with caution -- you ca confuse + * the handheld by doing weird things here. + */ + void setID(recordid_t id) + { + fID = id; + } + + /** Accessor for one bit of the record's attributes. Is this record marked + * deleted (on the handheld) ? Deleted records are not removed from the + * database until a HotSync is done (which normally calls purge deleted + * or so to really get rid of the records from storage. + */ + inline bool isDeleted() const + { + return fAttrib & dlpRecAttrDeleted; + } + + /** Accessor for one bit of the record's attributes. Is this record secret? + * Secret records are not displayed on the desktop by default. + */ + inline bool isSecret() const + { + return fAttrib & dlpRecAttrSecret; + } + + /** Accessor for one bit of the record's attributes. Is this record a + * to-be-archived record? When a record is deleted, it may be marked + * as "archive on PC" which means the PC should keep a copy. The + * PC data correspondng to an archived-but-deleted record must not + * be deleted. + */ + inline bool isArchived() const + { + return fAttrib & dlpRecAttrArchived; + } + + /** Accessor for one bit of the record's attributes. Is this record modified? + * Modified records are those that have been modified since the last HotSync. + */ + inline bool isModified() const + { + return fAttrib & dlpRecAttrDirty; + } + +#define SETTER(a) {\ + if (d) { fAttrib |= a; } \ + else { fAttrib &= ~a; } } + + /** Mark a record as deleted (or not).*/ + inline void setDeleted(bool d=true) SETTER(dlpRecAttrDeleted) + + /** Mark a record as secret (or not). */ + inline void setSecret(bool d=true) SETTER(dlpRecAttrSecret) + + /** Mark a record as archived (or not). */ + inline void setArchived(bool d=true) SETTER(dlpRecAttrArchived) + + /** Mark a record as modified (or not). */ + inline void setModified(bool d=true) SETTER(dlpRecAttrDirty) + +#undef SETTER + + /** Returns a text representation of this record. */ + virtual QString textRepresentation() const; + +private: + int fAttrib, fCat; + recordid_t fID; +} ; + +/** An "uninterpreted" representation of the bits comprising a HH record. +* This binary blob only exposes the data via the data() and size() functions, +* and also exposes the common characteristics of all entries. +*/ +class KDE_EXPORT PilotRecord : public PilotRecordBase +{ +public: + /** Constructor. Using the given @p data and @p length, create + * a record. Give it the additional attributes and category numbers; + * the UID is a HH unique ID for identifying records. + * + * This constructor makes a copy of the data buffer (and owns that buffer). + */ + PilotRecord(void* data, int length, int attrib, int cat, recordid_t uid) KDE_DEPRECATED; + + /** Constructor. Using the given buffer @p buf (which carries its + * own data and length), create a record. Otherwise much like the + * above constructor @em except that this record assumes ownership + * of the buffer, and doesn't make an additional copy + * (In practice, this just saves copying around extra buffers). + */ + PilotRecord(pi_buffer_t *buf, int attrib, int cat, recordid_t uid) : + PilotRecordBase(attrib,cat,uid), + fData((char *)buf->data), + fLen(buf->used), + fBuffer(buf) + { + fAllocated++; + } + + /** Constructor. Like the above, only take the attributes, category + * and id from the given @p entry. + */ + PilotRecord( pi_buffer_t *buf, const PilotRecordBase *entry ) : + PilotRecordBase( entry ), + fData((char *)buf->data), + fLen(buf->used), + fBuffer(buf) + { + fAllocated++; + } + + /** Destructor. Dispose of the buffers in the right form. */ + virtual ~PilotRecord() + { + if (fBuffer) + { + pi_buffer_free(fBuffer); + } + else + { + delete [] fData; + } + fDeleted++; + } + + /** Constructor. Copies the data from the @p orig record. */ + PilotRecord(PilotRecord* orig); + + /** Retrieve the data buffer for this record. Note that trying + * to change this data is fraught with peril -- especially trying + * to lengthen it. + * + * @see setData + */ + char *data() const + { + if (fBuffer) + { + return (char *)(fBuffer->data); + } + else + { + return fData; + } + } + + /** Returns the size of the data for this record. */ + int size() const + { + if (fBuffer) return fBuffer->used; else + return fLen; + } + + /** Returns the data buffer associated with this record. */ + const pi_buffer_t *buffer() const { return fBuffer; } + + /** Set the data for this record. Frees old data. Assumes + * ownership of the passed in buffer @p b. + */ + void setData(pi_buffer_t *b) + { + if (fBuffer) { pi_buffer_free(fBuffer); } + else { delete[] fData; } ; + fData = (char *)b->data; + fLen = b->used; + fBuffer = b; + } + + /** Assignment operator. Makes a copy of the @p orig record. */ + PilotRecord& operator=(PilotRecord& orig); + + /** Sets the data for this record. Makes a copy of the data buffer. */ + void setData(const char* data, int len); + + /** Returns a text representation of this record. */ + virtual QString textRepresentation() const; + +private: + char* fData; + int fLen; + pi_buffer_t *fBuffer; + +public: + /** + * This is an interface for tracking down memory leaks + * in the use of PilotRecords (for those without valgrind). + * Count the number of allocations and deallocations. + */ + static void allocationInfo(); +private: + static int fAllocated,fDeleted; +}; + +#endif diff --git a/kpilot/lib/pilotSerialDatabase.cc b/kpilot/lib/pilotSerialDatabase.cc new file mode 100644 index 000000000..a38898d1d --- /dev/null +++ b/kpilot/lib/pilotSerialDatabase.cc @@ -0,0 +1,432 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** Databases approached through DLP / Pilot-link look different, +** so this file defines an API for them. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ +#include "options.h" + +#include <time.h> +#include <iostream> + +#include <pi-dlp.h> + +#include <qfile.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kglobal.h> + +#include "pilotRecord.h" +#include "pilotSerialDatabase.h" +#include "kpilotdevicelink.h" + +PilotSerialDatabase::PilotSerialDatabase(KPilotDeviceLink *l, + const QString &dbName) : + PilotDatabase(dbName), + fDBName( dbName ), + fDBHandle(-1), + fDBSocket(l->pilotSocket()) +{ + FUNCTIONSETUP; + openDatabase(); +} + +PilotSerialDatabase::PilotSerialDatabase( KPilotDeviceLink *l, const DBInfo *info ) : + PilotDatabase( info ? Pilot::fromPilot( info->name ) : QString::null ), + fDBName( QString::null ), + fDBHandle( -1 ), + fDBSocket( l->pilotSocket() ) +{ + // Rather unclear why both the base class and this one have separate names. + fDBName = name(); + setDBOpen(false); + if (fDBName.isEmpty() || !info) + { + WARNINGKPILOT << "Bad database name requested." << endl; + return; + } + + int db; + if (dlp_OpenDB(fDBSocket, 0, dlpOpenReadWrite, info->name, &db) < 0) + { + WARNINGKPILOT << "Cannot open database on handheld." << endl; + return; + } + setDBHandle(db); + setDBOpen(true); +} + +PilotSerialDatabase::~PilotSerialDatabase() +{ + FUNCTIONSETUP; + closeDatabase(); +} + +QString PilotSerialDatabase::dbPathName() const +{ + QString s = CSL1("Pilot:"); + s.append(fDBName); + return s; +} + +// Reads the application block info +int PilotSerialDatabase::readAppBlock(unsigned char *buffer, int maxLen) +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return -1; + } + pi_buffer_t *buf = pi_buffer_new(maxLen); + int r = dlp_ReadAppBlock(fDBSocket, getDBHandle(), 0 /* offset */, maxLen, buf); + if (r>=0) + { + memcpy(buffer, buf->data, KMAX(maxLen, r)); + } + pi_buffer_free(buf); + return r; +} + +// Writes the application block info. +int PilotSerialDatabase::writeAppBlock(unsigned char *buffer, int len) +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return -1; + } + return dlp_WriteAppBlock(fDBSocket, getDBHandle(), buffer, len); +} + + // returns the number of records in the database +unsigned int PilotSerialDatabase::recordCount() const +{ + int idlen; + // dlp_ReadOpenDBInfo returns the number of bytes read and sets idlen to the # of recs + if (isOpen() && dlp_ReadOpenDBInfo(fDBSocket, getDBHandle(), &idlen)>0) + { + return idlen; + } + else + { + return 0; + } +} + + +// Returns a QValueList of all record ids in the database. +QValueList<recordid_t> PilotSerialDatabase::idList() +{ + QValueList<recordid_t> idlist; + int idlen=recordCount(); + if (idlen<=0) return idlist; + + recordid_t *idarr=new recordid_t[idlen]; + int idlenread; + int r = dlp_ReadRecordIDList (fDBSocket, getDBHandle(), 0, 0, idlen, idarr, &idlenread); + + if ( (r<0) || (idlenread<1) ) + { + WARNINGKPILOT << "Failed to read ID list from database." << endl; + return idlist; + } + + // now create the QValue list from the idarr: + for (idlen=0; idlen<idlenread; idlen++) + { + idlist.append(idarr[idlen]); + } + delete[] idarr; + return idlist; +} + + +// Reads a record from database by id, returns record length +PilotRecord *PilotSerialDatabase::readRecordById(recordid_t id) +{ + FUNCTIONSETUPL(3); + int index, attr, category; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return 0L; + } + if (id>0xFFFFFF) + { + WARNINGKPILOT << "Encountered an invalid record id " + << id << endl; + return 0L; + } + pi_buffer_t *b = pi_buffer_new(InitialBufferSize); + if (dlp_ReadRecordById(fDBSocket,getDBHandle(),id,b,&index,&attr,&category) >= 0) + { + return new PilotRecord(b, attr, category, id); + } + return 0L; +} + +// Reads a record from database, returns the record length +PilotRecord *PilotSerialDatabase::readRecordByIndex(int index) +{ + FUNCTIONSETUPL(3); + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return 0L; + } + + int attr, category; + recordid_t id; + PilotRecord *rec = 0L; + + pi_buffer_t *b = pi_buffer_new(InitialBufferSize); + if (dlp_ReadRecordByIndex(fDBSocket, getDBHandle(), index, + b, &id, &attr, &category) >= 0) + { + rec = new PilotRecord(b, attr, category, id); + } + + + return rec; +} + +// Reads the next record from database in category 'category' +PilotRecord *PilotSerialDatabase::readNextRecInCategory(int category) +{ + FUNCTIONSETUP; + int index, attr; + recordid_t id; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return 0L; + } + pi_buffer_t *b = pi_buffer_new(InitialBufferSize); + if (dlp_ReadNextRecInCategory(fDBSocket, getDBHandle(), + category,b,&id,&index,&attr) >= 0) + return new PilotRecord(b, attr, category, id); + return 0L; +} + +// Reads the next record from database that has the dirty flag set. +PilotRecord *PilotSerialDatabase::readNextModifiedRec(int *ind) +{ + FUNCTIONSETUP; + int index, attr, category; + recordid_t id; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return 0L; + } + pi_buffer_t *b = pi_buffer_new(InitialBufferSize); + if (dlp_ReadNextModifiedRec(fDBSocket, getDBHandle(), b, &id, &index, &attr, &category) >= 0) + { + if (ind) *ind=index; + return new PilotRecord(b, attr, category, id); + } + return 0L; +} + +// Writes a new record to database (if 'id' == 0 or id>0xFFFFFF, one will be assigned and returned in 'newid') +recordid_t PilotSerialDatabase::writeRecord(PilotRecord * newRecord) +{ + FUNCTIONSETUP; + recordid_t newid; + int success; + + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return 0; + } + // Do some sanity checking to prevent invalid UniqueIDs from being written + // to the handheld (RecordIDs are only 3 bytes!!!). Under normal conditions + // this check should never yield true, so write out an error to indicate + // someone messed up full time... + if (newRecord->id()>0xFFFFFF) + { + WARNINGKPILOT << "Encountered an invalid record id " + << newRecord->id() << ", resetting it to zero." << endl; + newRecord->setID(0); + } + success = + dlp_WriteRecord(fDBSocket, getDBHandle(), + newRecord->attributes(), newRecord->id(), + newRecord->category(), newRecord->data(), + newRecord->size(), &newid); + if ( (newRecord->id() != newid) && (newid!=0) ) + newRecord->setID(newid); + return newid; +} + +// Deletes a record with the given recordid_t from the database, or all records, if all is set to true. The recordid_t will be ignored in this case +int PilotSerialDatabase::deleteRecord(recordid_t id, bool all) +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT <<"DB not open"<<endl; + return -1; + } + return dlp_DeleteRecord(fDBSocket, getDBHandle(), all?1:0, id); +} + + +// Resets all records in the database to not dirty. +int PilotSerialDatabase::resetSyncFlags() +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return -1; + } + return dlp_ResetSyncFlags(fDBSocket, getDBHandle()); +} + +// Resets next record index to beginning +int PilotSerialDatabase::resetDBIndex() +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return -1; + } + return dlp_ResetDBIndex(fDBSocket, getDBHandle()); +} + +// Purges all Archived/Deleted records from Palm Pilot database +int PilotSerialDatabase::cleanup() +{ + FUNCTIONSETUP; + if (!isOpen()) + { + WARNINGKPILOT << "DB not open" << endl; + return -1; + } + return dlp_CleanUpDatabase(fDBSocket, getDBHandle()); +} + +void PilotSerialDatabase::openDatabase() +{ + FUNCTIONSETUP; + int db; + + setDBOpen(false); + + QString s = getDBName(); + if (s.isEmpty()) + { + WARNINGKPILOT << "Bad DB name, " << s << " string given." << endl; + return; + } + + QCString encodedName = QFile::encodeName(s); + if (encodedName.isEmpty()) + { + WARNINGKPILOT << "Bad DB name, " + << (encodedName.isNull() ? "null" : "empty") + << " string given." + << endl; + return; + } + + char encodedNameBuffer[PATH_MAX]; + strlcpy(encodedNameBuffer,(const char *)encodedName,PATH_MAX); + + DEBUGKPILOT << fname << ": opening database: [" + << encodedNameBuffer << "]" << endl; + + if (dlp_OpenDB(fDBSocket, 0, dlpOpenReadWrite, + encodedNameBuffer, &db) < 0) + { + WARNINGKPILOT << "Cannot open database on handheld." << endl; + return; + } + setDBHandle(db); + setDBOpen(true); +} + +bool PilotSerialDatabase::createDatabase(long creator, long type, int cardno, int flags, int version) +{ + FUNCTIONSETUP; + int db; + + // if the database is already open, we cannot create it again. How about completely resetting it? (i.e. deleting it and the createing it again) + if (isOpen()) return true; + // The latin1 seems ok, database names are latin1. + int res=dlp_CreateDB(fDBSocket, + creator, type, cardno, flags, version, + Pilot::toPilot(getDBName()), &db); + if (res<0) { + WARNINGKPILOT << "Cannot create database " << getDBName() << " on the handheld" << endl; + return false; + } + // TODO: Do I have to open it explicitly??? + setDBHandle(db); + setDBOpen(true); + return true; +} + +void PilotSerialDatabase::closeDatabase() +{ + FUNCTIONSETUP; + if (!isOpen() ) + { + return; + } + + DEBUGKPILOT << fname << ": Closing DB handle #" << getDBHandle() << endl; + dlp_CloseDB(fDBSocket, getDBHandle()); + DEBUGKPILOT << fname << ": after closing" << endl; + setDBOpen(false); +} + +int PilotSerialDatabase::deleteDatabase() +{ + FUNCTIONSETUP; + + if (isOpen()) closeDatabase(); + + return dlp_DeleteDB(fDBSocket, 0, Pilot::toPilot(fDBName)); +} + + + +/* virtual */ PilotDatabase::DBType PilotSerialDatabase::dbType() const +{ + return eSerialDB; +} + diff --git a/kpilot/lib/pilotSerialDatabase.h b/kpilot/lib/pilotSerialDatabase.h new file mode 100644 index 000000000..51a6ba26d --- /dev/null +++ b/kpilot/lib/pilotSerialDatabase.h @@ -0,0 +1,144 @@ +#ifndef _KPILOT_PILOTSERIALDATABASE_H +#define _KPILOT_PILOTSERIALDATABASE_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +** See the .cc file for an explanation of what this file is for. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + + +#include "pilotDatabase.h" +#include "pilotRecord.h" + +/** @file +* Database class for a database on the pilot connected +* via the serial port (ie: hot-sync cradle) +*/ + +class KPilotDeviceLink; + +/** +* PilotSerialDatabase represents databases stored on the handheld +* and accessed through the SLP / DLP protocol. +*/ +class KDE_EXPORT PilotSerialDatabase : public PilotDatabase +{ +friend class KPilotDeviceLink; +protected: + PilotSerialDatabase( KPilotDeviceLink *l, const QString &dbName ); + PilotSerialDatabase( KPilotDeviceLink *l, const DBInfo *info ); + +public: + virtual ~PilotSerialDatabase(); + + /** Reads the application block info, returns size */ + virtual int readAppBlock(unsigned char* buffer, int maxLen); + /** Writes the application block info. */ + virtual int writeAppBlock(unsigned char* buffer, int len); + /** returns the number of records in the database, 0 if not open */ + virtual unsigned int recordCount() const; + /** Returns a QValueList of all record ids in the database. */ + virtual QValueList<recordid_t> idList(); + /** Reads a record from database by id, returns record length */ + virtual PilotRecord* readRecordById(recordid_t id); + /** Reads a record from database, returns the record length */ + virtual PilotRecord* readRecordByIndex(int index); + /** Reads the next record from database in category 'category' */ + virtual PilotRecord* readNextRecInCategory(int category); + /** + * Reads the next record from database that has the dirty flag set. + * ind (if a valid pointer is given) will receive the index of the + * returned record. + */ + virtual PilotRecord* readNextModifiedRec(int *ind=NULL); + + /** + * Writes a new record to database (if 'id' == 0, one will be + * assigned to newRecord) + */ + virtual recordid_t writeRecord(PilotRecord* newRecordb); + + /** + * Deletes a record with the given recordid_t from the database, + * or all records, if all is set to true. The recordid_t will be + * ignored in this case. Return value is negative on error, 0 otherwise. + */ + virtual int deleteRecord(recordid_t id, bool all=false); + /** Resets all records in the database to not dirty. */ + virtual int resetSyncFlags(); + /** Resets next record index to beginning */ + virtual int resetDBIndex(); + /** Purges all Archived/Deleted records from Palm Pilot database */ + virtual int cleanup(); + + virtual QString dbPathName() const; + + /** + * Deletes the database (by name, as given in the constructor and + * stored in the fDBName field). + */ + virtual int deleteDatabase(); + + /** + * Creates the database with the given creator, type and flags on + * the given card (default is RAM). If the database already exists, + * this function does nothing. + */ + virtual bool createDatabase(long creator=0, + long type=0, int cardno=0, int flags=0, int version=0); + QString getDBName() { return fDBName; } + + + virtual DBType dbType() const; + +protected: + virtual void openDatabase(); + virtual void closeDatabase(); + /** Returns the file handle used to communicate with the handheld. + * This is an internal value to be passed to DLP functions. + */ + int getDBHandle() const + { + return fDBHandle; + } + + +private: + void setDBHandle(int handle) { fDBHandle = handle; } + + QString fDBName; + int fDBHandle; + int fDBSocket; + // Pilot-link 0.12 allocates buffers as needed and resizes them. + // Start with a buffer that is _probably_ big enough for most + // PIM records, but much smaller than the 64k that we use otherwise. + // Might want to add algorithm for trying to optimize the initial + // allocation for a given database. + static const int InitialBufferSize = 2048; +}; + +#endif diff --git a/kpilot/lib/pilotSysInfo.h b/kpilot/lib/pilotSysInfo.h new file mode 100644 index 000000000..4deb4f1b4 --- /dev/null +++ b/kpilot/lib/pilotSysInfo.h @@ -0,0 +1,144 @@ +#ifndef _KPILOT_SYSINFO_H +#define _KPILOT_SYSINFO_H +/* sysInfo.h KPilot +** +** Copyright (C) 2003 by Reinhold Kainhofer +** +** Wrapper for pilot-link's SysInfo Structure +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <pi-version.h> +#include <pi-dlp.h> + + + +class KPilotSysInfo +{ +public: + /** Constructor. Create an empty SysInfo structure. */ + KPilotSysInfo() + { + ::memset(&fSysInfo,0,sizeof(struct SysInfo)); + } + + /** Constructor. Copy an existing pilot-link SysInfo structure. + * Ownership is not changed. @p sys_info may be NULL. + */ + KPilotSysInfo(const SysInfo *sys_info) + { + ::memset(&fSysInfo,0,sizeof(struct SysInfo)); + if (sys_info) + { + fSysInfo = *sys_info; + } + } + + /** Access to the raw SysInfo structure. */ + SysInfo *sysInfo() + { + return &fSysInfo; + } + + /** Get the ROM version of the handheld. This is a pilot-link + * long value (4 bytes) with major, minor, bugfix version + * numbers encoded in the value. + */ + const unsigned long getRomVersion() const + { + return fSysInfo.romVersion; + } + + /** Get the locale number of the handheld. + * @note I do not know what this means. + */ + const unsigned long getLocale() const + { + return fSysInfo.locale; + } + /** Set the locale number of the handheld. + * @note I do not know what this means. + */ + void setLocale(unsigned long newval) + { + fSysInfo.locale=newval; + } + + /** Get the length (in bytes) of the ProductID string. */ + const int getProductIDLength() const + { + return fSysInfo.prodIDLength; + } + /** Get the ProductID string from the handheld. This is + * guaranteed to be NUL terminated. + */ + const char* getProductID() const + { + return fSysInfo.prodID; + } + + /** Accessor for the major version of the DLP protocol in use. */ + const unsigned short getMajorVersion() const + { + return fSysInfo.dlpMajorVersion; + } + /** Accessor for the minor version of the DLP protocol in use. */ + const unsigned short getMinorVersion() const + { + return fSysInfo.dlpMinorVersion; + } + + /** Accessor for the major compatibility version of the handheld. + * @note I do not know what this means. + */ + const unsigned short getCompatMajorVersion() const + { + return fSysInfo.compatMajorVersion; + } + /** Accessor for the minor compatibility version of the handheld. + * @note I do not know what this means. + */ + const unsigned short getCompatMinorVersion() const + { + return fSysInfo.compatMinorVersion; + } + + + /** Returns the maximum record size that the handheld supports. + * Normally this is 65524 or so (which means that larger values + * don't necessarily @em fit in a short). + */ + const unsigned short getMaxRecSize() const + { + return fSysInfo.maxRecSize; + } + +private: + struct SysInfo fSysInfo; +}; + +#endif diff --git a/kpilot/lib/pilotTodoEntry.cc b/kpilot/lib/pilotTodoEntry.cc new file mode 100644 index 000000000..f4c5596fb --- /dev/null +++ b/kpilot/lib/pilotTodoEntry.cc @@ -0,0 +1,270 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This is a C++ wrapper for the todo-list entry structures. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ +#include "options.h" + + +#include <stdlib.h> + +#include <qdatetime.h> +#include <qnamespace.h> + +#include <kglobal.h> +#include <kdebug.h> + + +#include "pilotTodoEntry.h" + + +PilotTodoEntry::PilotTodoEntry() : + fDescriptionSize(0), + fNoteSize(0) +{ + FUNCTIONSETUP; + ::memset(&fTodoInfo, 0, sizeof(struct ToDo)); +} + +PilotTodoEntry::PilotTodoEntry(PilotRecord * rec) : + PilotRecordBase(rec), + fDescriptionSize(0), + fNoteSize(0) +{ + ::memset(&fTodoInfo, 0, sizeof(struct ToDo)); + if (rec) + { + pi_buffer_t b; + b.data = (unsigned char *) rec->data(); + b.allocated = b.used = rec->size(); + unpack_ToDo(&fTodoInfo, &b, todo_v1); + if (fTodoInfo.description) + { + // Assume size of buffer allocated is just large enough; + // count trailing NUL as well. + fDescriptionSize = strlen(fTodoInfo.description)+1; + } + if (fTodoInfo.note) + { + // Same + fNoteSize = strlen(fTodoInfo.note)+1; + } + } + +} + + +PilotTodoEntry::PilotTodoEntry(const PilotTodoEntry & e) : + PilotRecordBase( &e ), + fDescriptionSize(0), + fNoteSize(0) +{ + FUNCTIONSETUP; + ::memcpy(&fTodoInfo, &e.fTodoInfo, sizeof(fTodoInfo)); + // See PilotDateEntry::operator = for details + fTodoInfo.description = 0L; + fTodoInfo.note = 0L; + + setDescriptionP(e.getDescriptionP()); + setNoteP(e.getNoteP()); +} + + +PilotTodoEntry & PilotTodoEntry::operator = (const PilotTodoEntry & e) +{ + if (this != &e) + { + KPILOT_FREE(fTodoInfo.description); + KPILOT_FREE(fTodoInfo.note); + + ::memcpy(&fTodoInfo, &e.fTodoInfo, sizeof(fTodoInfo)); + // See PilotDateEntry::operator = for details + fTodoInfo.description = 0L; + fTodoInfo.note = 0L; + fDescriptionSize = 0; + fNoteSize = 0; + + setDescriptionP(e.getDescriptionP()); + setNoteP(e.getNoteP()); + + } + + return *this; +} + +QString PilotTodoEntry::getTextRepresentation(Qt::TextFormat richText) +{ + QString text, tmp; + QString par = (richText==Qt::RichText) ?CSL1("<p>"): QString(); + QString ps = (richText==Qt::RichText) ?CSL1("</p>"):CSL1("\n"); + QString br = (richText==Qt::RichText) ?CSL1("<br/>"):CSL1("\n"); + + // title + name + text += par; + tmp= (richText==Qt::RichText) ?CSL1("<b><big>%1</big></b>"):CSL1("%1"); + text += tmp.arg(rtExpand(getDescription(), richText)); + text += ps; + + text += par; + if (getComplete()) + text += i18n("Completed"); + else + text += i18n("Not completed"); + text += ps; + + if (!getIndefinite()) + { + QDate dt(readTm(getDueDate()).date()); + QString dueDate(dt.toString(Qt::LocalDate)); + text+=par; + text+=i18n("Due date: %1").arg(dueDate); + text+=ps; + } + + text+=par; + text+=ps; + + text+=par; + text+=i18n("Priority: %1").arg(getPriority()); + text+=ps; + + if (!getNote().isEmpty()) + { + text += (richText==Qt::RichText) ?CSL1("<hr/>"):CSL1("-------------------------\n"); + text+=par; + text+= (richText==Qt::RichText) ?i18n("<b><em>Note:</em></b><br>"):i18n("Note:\n"); + text+=rtExpand(getNote(), richText); + text+=ps; + } + + return text; +} + +PilotRecord *PilotTodoEntry::pack() const +{ + int i; + + pi_buffer_t *b = pi_buffer_new( sizeof(fTodoInfo) ); + i = pack_ToDo(const_cast<ToDo_t *>(&fTodoInfo), b, todo_v1); + if (i<0) + { + return 0; + } + // pack_ToDo sets b->used + return new PilotRecord( b, this ); +} + +void PilotTodoEntry::setDescription(const QString &desc) +{ + if (desc.length() < fDescriptionSize) + { + Pilot::toPilot(desc, fTodoInfo.description, fDescriptionSize); + } + else + { + setDescriptionP(Pilot::toPilot(desc),desc.length()); + } +} + +void PilotTodoEntry::setDescriptionP(const char *desc, int len) +{ + KPILOT_FREE(fTodoInfo.description); + if (desc && *desc) + { + if (-1 == len) + { + len=::strlen(desc); + } + + fDescriptionSize = len+1; + fTodoInfo.description = (char *)::malloc(len + 1); + if (fTodoInfo.description) + { + strncpy(fTodoInfo.description, desc, len); + fTodoInfo.description[len] = 0; + } + else + { + WARNINGKPILOT << "malloc() failed, description not set" + << endl; + } + } + else + { + fTodoInfo.description = 0L; + } +} + +QString PilotTodoEntry::getDescription() const +{ + return Pilot::fromPilot(getDescriptionP()); +} + +void PilotTodoEntry::setNote(const QString ¬e) +{ + if (note.length() < fNoteSize) + { + Pilot::toPilot(note, fTodoInfo.note, fNoteSize); + } + else + { + setNoteP(Pilot::toPilot(note),note.length()); + } +} + +void PilotTodoEntry::setNoteP(const char *note, int len) +{ + KPILOT_FREE(fTodoInfo.note); + if (note && *note) + { + if (-1 == len) + { + len=::strlen(note); + } + + fNoteSize = len+1; + fTodoInfo.note = (char *)::malloc(len + 1); + if (fTodoInfo.note) + { + strncpy(fTodoInfo.note, note, len); + fTodoInfo.note[len] = 0; + } + else + { + WARNINGKPILOT << "malloc() failed, note not set" << endl; + } + } + else + { + fTodoInfo.note = 0L; + } +} + +QString PilotTodoEntry::getNote() const +{ + return Pilot::fromPilot(getNoteP()); +} + diff --git a/kpilot/lib/pilotTodoEntry.h b/kpilot/lib/pilotTodoEntry.h new file mode 100644 index 000000000..3735771b6 --- /dev/null +++ b/kpilot/lib/pilotTodoEntry.h @@ -0,0 +1,166 @@ +#ifndef _KPILOT_PILOTTODOENTRY_H +#define _KPILOT_PILOTTODOENTRY_H +/* pilotTodoEntry.h -*- C++ -*- KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This is a wrapper around the pilot-link Memo structure. It is +** the interpreted form of a Pilot database record. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <time.h> + +#include <pi-macros.h> +#include <pi-todo.h> + +#include <qnamespace.h> +#include <qstring.h> + +#include "pilotRecord.h" +#include "pilotAppInfo.h" + +/** @file This file defines structures wrapped around the ToDo database +* on the Pilot, based on pilot-link's ToDo stuff. +*/ + +/** A decoded ToDo item. */ +class KDE_EXPORT PilotTodoEntry : public PilotRecordBase +{ +public: + /** Create an empty ToDo item. All attributes are 0. */ + PilotTodoEntry(); + + /** + * Constructor. Create a ToDo item and fill it with data from the + * uninterpreted record @p rec. The record may be NULL, in which + * case the todo is empty and its category and ID are zero, as in + * the constructor above. + */ + PilotTodoEntry(PilotRecord * rec); + + /** Copy an existing ToDo item. */ + PilotTodoEntry(const PilotTodoEntry &e); + + /** Delete a ToDo item. */ + ~PilotTodoEntry() + { + free_ToDo(&fTodoInfo); + } + + /** Return a string for the ToDo item. If @param richText is true, then + * use qt style markup to make the string clearer when displayed. + */ + QString getTextRepresentation(Qt::TextFormat richText); + + /** Assign an existing ToDo item to this one. */ + PilotTodoEntry& operator=(const PilotTodoEntry &e); + + /** Accessor for the Due Date of the ToDo item. */ + struct tm getDueDate() const { return fTodoInfo.due; } + + /** Set the Due Date for the ToDo item. */ + void setDueDate(struct tm& d) + { + fTodoInfo.due = d; + } + + /** Return the indefinite status of the ToDo (? that is, whether it + * had a Due Date that is relevant or not). Return values are 0 + * (not indefinite) or non-0. + */ + int getIndefinite() const + { + return fTodoInfo.indefinite; + } + + /** Set whether the ToDo is indefinite or not. */ + void setIndefinite(int i) + { + fTodoInfo.indefinite = i; + } + + /** Return the priority of the ToDo item. The priority ranges + * from 1-5 on the handheld, so this needs to be mapped (perhaps) + * onto KOrganizer's priority levels. + */ + int getPriority() const + { + return fTodoInfo.priority; + } + + /** Set the priority of the ToDo. */ + void setPriority(int p) + { + fTodoInfo.priority = p; + } + + /** Return whether the ToDo is complete (done, finished) or not. */ + int getComplete() const + { + return fTodoInfo.complete; + } + + /** Set whether the ToDo is done. */ + void setComplete(int c) + { + fTodoInfo.complete = c; + } + + /** Get the ToDo item's description (which is the title shown on + * the handheld, and the item's Title in KDE). This uses the default codec. + */ + QString getDescription() const; + /** Set the ToDo item's description. */ + void setDescription(const QString &); + + /** Get the ToDo item's note (the longer text, not immediately accessible + * on the handheld). This uses the default codec. + */ + QString getNote() const; + + /** Set the ToDo item's note. */ + void setNote(const QString ¬e); + + /** Returns the label for the category this ToDo item is in. */ + QString getCategoryLabel() const; + + PilotRecord *pack() const; + +protected: + const char *getDescriptionP() const { return fTodoInfo.description; } ; + void setDescriptionP(const char *, int len=-1) ; + const char *getNoteP() const { return fTodoInfo.note; } ; + void setNoteP(const char *, int len=-1) ; + +private: + struct ToDo fTodoInfo; + unsigned int fDescriptionSize, fNoteSize; +}; + +typedef PilotAppInfo<ToDoAppInfo,unpack_ToDoAppInfo, pack_ToDoAppInfo> PilotToDoInfo; + + +#endif + diff --git a/kpilot/lib/pilotUser.h b/kpilot/lib/pilotUser.h new file mode 100644 index 000000000..6ba46b007 --- /dev/null +++ b/kpilot/lib/pilotUser.h @@ -0,0 +1,128 @@ +#ifndef _KPILOT_PILOTUSER_H +#define _KPILOT_PILOTUSER_H +/* pilotUser.h KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** +** Wrapper for the PilotUser struct from pilot-link, which describes +** the user-data set in the Pilot. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <pi-dlp.h> + +#include "pilot.h" + +class KPilotUser +{ +public: + /** Constructor. Create an empty PilotUser structure. */ + KPilotUser() + { + ::memset(&fUser,0,sizeof(struct PilotUser)); + } + /** Constructor. Use the given PilotUser structure. + * This creates a copy; no ownership is transferred. + */ + KPilotUser(const PilotUser *user) + { + fUser = *user; + } + + /** Accessor for the whole PilotUser structure. */ + PilotUser *data() + { + return &fUser; + } + + /** @return The username set on the handheld. */ + QString name() const + { + return Pilot::fromPilot( fUser.username ); + } + /** Set the user name to the given @p name , truncated + * if necessary to the size of the field on the handheld. + */ + void setName( const QString &name ) + { + Pilot::toPilot( name, fUser.username, sizeof(fUser.username) ); + } + + /** @return The length of the password on the handheld, + * in bytes. + */ + const int passwordLength() const + { + return fUser.passwordLength; + } + + /** @return the ID (4 bytes) of the last PC to sync this handheld. + * This is intended to help identify when the use has + * changed PCs and needs a new full sync. + */ + unsigned long getLastSyncPC() const + { + return fUser.lastSyncPC; + } + /** Set the ID of the PC syncing the handheld to @p pc . This + * should be unique in some way (perhaps IP addresses can be + * used this way, or hostnames). + */ + void setLastSyncPC(unsigned long pc) + { + fUser.lastSyncPC = pc; + } + + /** @return the timestamp that the handheld was last synced + * successfully. + */ + time_t getLastSuccessfulSyncDate() + { + return fUser.successfulSyncDate; + } + /** Set the timestamp for a successful sync. */ + void setLastSuccessfulSyncDate(time_t when) + { + fUser.successfulSyncDate = when; + } + + /** @return the timestamp of the last sync attempt. */ + time_t getLastSyncDate() + { + return fUser.lastSyncDate; + } + /** Set the timestamp of the sync attempt. */ + void setLastSyncDate(time_t when) + { + fUser.lastSyncDate = when; + } + +private: + struct PilotUser fUser; +}; + +#endif diff --git a/kpilot/lib/plugin.cc b/kpilot/lib/plugin.cc new file mode 100644 index 000000000..e9bcc9221 --- /dev/null +++ b/kpilot/lib/plugin.cc @@ -0,0 +1,760 @@ +/* KPilot +** +** Copyright (C) 2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +** This file defines the base class of all KPilot conduit plugins configuration +** dialogs. This is necessary so that we have a fixed API to talk to from +** inside KPilot. +** +** The factories used by KPilot plugins are also documented here. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <stdlib.h> + +#include <qdir.h> +#include <qfileinfo.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <qtabwidget.h> +#include <qtextview.h> +#include <qtimer.h> + +#include <dcopclient.h> +#include <kaboutapplication.h> +#include <kactivelabel.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kinstance.h> +#include <klibloader.h> +#include <kmessagebox.h> +#include <kservice.h> +#include <kservicetype.h> +#include <kstandarddirs.h> + +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" + +#include "plugin.moc" + +ConduitConfigBase::ConduitConfigBase(QWidget *parent, + const char *name) : + QObject(parent,name), + fModified(false), + fWidget(0L), + fConduitName(i18n("Unnamed")) +{ + FUNCTIONSETUP; +} + +ConduitConfigBase::~ConduitConfigBase() +{ + FUNCTIONSETUP; +} + +/* slot */ void ConduitConfigBase::modified() +{ + fModified=true; + emit changed(true); +} + +/* virtual */ QString ConduitConfigBase::maybeSaveText() const +{ + FUNCTIONSETUP; + + return i18n("<qt>The <i>%1</i> conduit's settings have been changed. Do you " + "want to save the changes before continuing?</qt>").arg(this->conduitName()); +} + +/* virtual */ bool ConduitConfigBase::maybeSave() +{ + FUNCTIONSETUP; + + if (!isModified()) return true; + + int r = KMessageBox::questionYesNoCancel(fWidget, + maybeSaveText(), + i18n("%1 Conduit").arg(this->conduitName()), KStdGuiItem::save(), KStdGuiItem::discard()); + if (r == KMessageBox::Cancel) return false; + if (r == KMessageBox::Yes) commit(); + return true; +} + +QWidget *ConduitConfigBase::aboutPage(QWidget *parent, KAboutData *ad) +{ + FUNCTIONSETUP; + + QWidget *w = new QWidget(parent, "aboutpage"); + + QString s; + QLabel *text; + KIconLoader *l = KGlobal::iconLoader(); + const KAboutData *p = ad ? ad : KGlobal::instance()->aboutData(); + + QGridLayout *grid = new QGridLayout(w, 5, 4, SPACING); + + grid->addColSpacing(0, SPACING); + grid->addColSpacing(4, SPACING); + + + QPixmap applicationIcon = + l->loadIcon(QString::fromLatin1(p->appName()), + KIcon::Desktop, + 64, KIcon::DefaultState, 0L, + true); + + if (applicationIcon.isNull()) + { + applicationIcon = l->loadIcon(QString::fromLatin1("kpilot"), + KIcon::Desktop); + } + + text = new QLabel(w); + // Experiment with a long non-<qt> string. Use that to find + // sensible widths for the columns. + // + text->setText(i18n("Send questions and comments to kdepim-users@kde.org")); + text->adjustSize(); + + int linewidth = text->size().width(); + int lineheight = text->size().height(); + + // Use the label to display the applciation icon + text->setText(QString::null); + text->setPixmap(applicationIcon); + text->adjustSize(); + grid->addWidget(text, 0, 1); + + + KActiveLabel *linktext = new KActiveLabel(w); + grid->addRowSpacing(1,kMax(100,6*lineheight)); + grid->addRowSpacing(2,kMax(100,6*lineheight)); + grid->addColSpacing(2,SPACING+linewidth/2); + grid->addColSpacing(3,SPACING+linewidth/2); + grid->setRowStretch(1,50); + grid->setRowStretch(2,50); + grid->setColStretch(2,50); + grid->setColStretch(3,50); + linktext->setMinimumSize(linewidth,kMax(260,60+12*lineheight)); + linktext->setFixedHeight(kMax(260,60+12*lineheight)); + linktext->setVScrollBarMode(QScrollView::Auto/*AlwaysOn*/); + text = new QLabel(w); + grid->addMultiCellWidget(text,0,0,2,3); + grid->addMultiCellWidget(linktext,1,2,1,3); + + // Now set the program and copyright information. + s = CSL1("<qt><h3>"); + s += p->programName(); + s += ' '; + s += p->version(); + s += CSL1("</h3>"); + s += p->copyrightStatement() + CSL1("<br></qt>"); + text->setText(s); + + linktext->append(p->shortDescription() + CSL1("<br>")); + + if (!p->homepage().isEmpty()) + { + s = QString::null; + s += CSL1("<a href=\"%1\">").arg(p->homepage()); + s += p->homepage(); + s += CSL1("</a><br>"); + linktext->append(s); + } + + s = QString::null; + s += i18n("Send questions and comments to <a href=\"mailto:%1\">%2</a>.") + .arg( CSL1("kdepim-users@kde.org") ) + .arg( CSL1("kdepim-users@kde.org") ); + s += ' '; + s += i18n("Send bug reports to <a href=\"mailto:%1\">%2</a>.") + .arg(p->bugAddress()) + .arg(p->bugAddress()); + s += ' '; + s += i18n("For trademark information, see the " + "<a href=\"help:/kpilot/trademarks.html\">KPilot User's Guide</a>."); + s += CSL1("<br>"); + linktext->append(s); + linktext->append(QString::null); + + + + QValueList<KAboutPerson> pl = p->authors(); + QValueList<KAboutPerson>::ConstIterator i; + + s = i18n("<b>Authors:</b> "); + + QString comma = CSL1(", "); + + unsigned int count=1; + for (i=pl.begin(); i!=pl.end(); ++i) + { + s.append(CSL1("%1 (<i>%2</i>)%3") + .arg((*i).name()) + .arg((*i).task()) + .arg(count<pl.count() ? comma : QString::null) + ); + count++; + } + linktext->append(s); + + s = QString::null; + pl = p->credits(); + if (pl.count()>0) + { + count=1; + s.append(i18n("<b>Credits:</b> ")); + for (i=pl.begin(); i!=pl.end(); ++i) + { + s.append(CSL1("%1 (<i>%2</i>)%3") + .arg((*i).name()) + .arg((*i).task()) + .arg(count<pl.count() ? comma : QString::null) + ); + count++; + } + } + linktext->append(s); + linktext->ensureVisible(0,0); + + w->adjustSize(); + + return w; +} + +/* static */ void ConduitConfigBase::addAboutPage(QTabWidget *tw, + KAboutData *ad) +{ + FUNCTIONSETUP; + + Q_ASSERT(tw); + + QWidget *w = aboutPage(tw,ad); + QSize sz = w->size(); + + if (sz.width() < tw->size().width()) + { + sz.setWidth(tw->size().width()); + } + if (sz.height() < tw->size().height()) + { + sz.setHeight(tw->size().height()); + } + + tw->resize(sz); + tw->addTab(w, i18n("About")); + tw->adjustSize(); +} + + + +ConduitAction::ConduitAction(KPilotLink *p, + const char *name, + const QStringList &args) : + SyncAction(p,name), + fDatabase(0L), + fLocalDatabase(0L), + fCtrHH(0L), + fCtrPC(0L), + fSyncDirection(args), + fConflictResolution(SyncAction::eAskUser), + fFirstSync(false) +{ + FUNCTIONSETUP; + + QString cResolution(args.grep(QRegExp(CSL1("--conflictResolution \\d*"))).first()); + if (cResolution.isEmpty()) + { + fConflictResolution=(SyncAction::ConflictResolution) + cResolution.replace(QRegExp(CSL1("--conflictResolution (\\d*)")), CSL1("\\1")).toInt(); + } + + for (QStringList::ConstIterator it = args.begin(); + it != args.end(); + ++it) + { + DEBUGKPILOT << fname << ": " << *it << endl; + } + + DEBUGKPILOT << fname << ": Direction=" << fSyncDirection.name() << endl; + fCtrHH = new CUDCounter(i18n("Handheld")); + fCtrPC = new CUDCounter(i18n("PC")); +} + +/* virtual */ ConduitAction::~ConduitAction() +{ + FUNCTIONSETUP; + + KPILOT_DELETE(fDatabase); + KPILOT_DELETE(fLocalDatabase); + + KPILOT_DELETE(fCtrHH); + KPILOT_DELETE(fCtrPC); +} + +bool ConduitAction::openDatabases(const QString &name, bool *retrieved) +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname + << ": Trying to open database " + << name << endl; + DEBUGKPILOT << fname + << ": Mode=" + << (syncMode().isTest() ? "test " : "") + << (syncMode().isLocal() ? "local " : "") + << endl ; + + KPILOT_DELETE(fLocalDatabase); + + QString localPathName = PilotLocalDatabase::getDBPath() + name; + + // we always want to use the conduits/ directory for our local + // databases. this keeps our backups and data that our conduits use + // for record keeping separate + localPathName.replace(CSL1("DBBackup/"), CSL1("conduits/")); + + DEBUGKPILOT << fname << ": localPathName: [" << localPathName + << "]" << endl; + + PilotLocalDatabase *localDB = new PilotLocalDatabase( localPathName ); + + if (!localDB) + { + WARNINGKPILOT << "Could not initialize object for local copy of database \"" + << name + << "\"" << endl; + if (retrieved) *retrieved = false; + return false; + } + + // if there is no backup db yet, fetch it from the palm, open it and set the full sync flag. + if (!localDB->isOpen() ) + { + QString dbpath(localDB->dbPathName()); + KPILOT_DELETE(localDB); + DEBUGKPILOT << fname + << ": Backup database " << dbpath + << " not found." << endl; + struct DBInfo dbinfo; + +// TODO Extend findDatabase() with extra overload? + if (deviceLink()->findDatabase(Pilot::toPilot( name ), &dbinfo)<0 ) + { + WARNINGKPILOT << "Could not get DBInfo for " << name << endl; + if (retrieved) *retrieved = false; + return false; + } + + DEBUGKPILOT << fname + << ": Found Palm database: " << dbinfo.name <<endl + << fname << ": type = " << dbinfo.type + << " creator = " << dbinfo.creator + << " version = " << dbinfo.version + << " index = " << dbinfo.index << endl; + dbinfo.flags &= ~dlpDBFlagOpen; + + // make sure the dir for the backup db really exists! + QFileInfo fi(dbpath); + QString path(QFileInfo(dbpath).dir(true).absPath()); + if (!path.endsWith(CSL1("/"))) path.append(CSL1("/")); + if (!KStandardDirs::exists(path)) + { + DEBUGKPILOT << fname << ": Trying to create path for database: <" + << path << ">" << endl; + KStandardDirs::makeDir(path); + } + if (!KStandardDirs::exists(path)) + { + DEBUGKPILOT << fname << ": Database directory does not exist." << endl; + if (retrieved) *retrieved = false; + return false; + } + + if (!deviceLink()->retrieveDatabase(dbpath, &dbinfo) ) + { + WARNINGKPILOT << "Could not retrieve database " + << name << " from the handheld." << endl; + if (retrieved) *retrieved = false; + return false; + } + localDB = new PilotLocalDatabase( localPathName ); + if (!localDB || !localDB->isOpen()) + { + WARNINGKPILOT << "local backup of database " << name << " could not be initialized." << endl; + if (retrieved) *retrieved = false; + return false; + } + if (retrieved) *retrieved=true; + } + fLocalDatabase = localDB; + + fDatabase = deviceLink()->database( name ); + + if (!fDatabase) + { + WARNINGKPILOT << "Could not open database \"" + << name + << "\" on the pilot." + << endl; + } + else + { + fCtrHH->setStartCount(fDatabase->recordCount()); + } + + return (fDatabase && fDatabase->isOpen() && + fLocalDatabase && fLocalDatabase->isOpen() ); +} + + +bool ConduitAction::changeSync(SyncMode::Mode m) +{ + FUNCTIONSETUP; + + if ( fSyncDirection.isSync() && SyncMode::eFullSync == m) + { + fSyncDirection.setMode(m); + return true; + } + return false; +} + +void ConduitAction::finished() +{ + FUNCTIONSETUP; + + if (fDatabase && fCtrHH) + fCtrHH->setEndCount(fDatabase->recordCount()); + + if (fCtrHH && fCtrPC) + { + addSyncLogEntry(fCtrHH->moo() +"\n",false); + DEBUGKPILOT << fname << ": " << fCtrHH->moo() << endl; + addSyncLogEntry(fCtrPC->moo() +"\n",false); + DEBUGKPILOT << fname << ": " << fCtrPC->moo() << endl; + + // STEP2 of making sure we don't delete our little user's + // precious data... + // sanity checks for handheld... + int hhVolatility = fCtrHH->percentDeleted() + + fCtrHH->percentUpdated() + + fCtrHH->percentCreated(); + + int pcVolatility = fCtrPC->percentDeleted() + + fCtrPC->percentUpdated() + + fCtrPC->percentCreated(); + + // TODO: allow user to configure this... + // this is a percentage... + int allowedVolatility = 70; + + QString caption = i18n("Large Changes Detected"); + // args are already i18n'd + QString query = i18n("The %1 conduit has made a " + "large number of changes to your %2. Do you want " + "to allow this change?\nDetails:\n\t%3"); + + if (hhVolatility > allowedVolatility) + { + query = query.arg(fConduitName) + .arg(fCtrHH->type()).arg(fCtrHH->moo()); + + DEBUGKPILOT << fname << ": Yikes, lots of volatility " + << "caught. Check with user: [" << query + << "]." << endl; + + /* + int rc = questionYesNo(query, caption, + QString::null, 0 ); + if (rc == KMessageBox::Yes) + { + // TODO: add commit and rollback code. + // note: this will require some thinking, + // since we have to undo changes to the + // pilot databases, changes to the PC + // resources, changes to the mappings files + // (record id mapping, etc.) + } + */ + } + + + } + +} + + +ConduitProxy::ConduitProxy(KPilotLink *p, + const QString &name, + const SyncAction::SyncMode &m) : + ConduitAction(p,name.latin1(),m.list()), + fDesktopName(name) +{ + FUNCTIONSETUP; +} + +/* virtual */ bool ConduitProxy::exec() +{ + FUNCTIONSETUP; + + // query that service + KSharedPtr < KService > o = KService::serviceByDesktopName(fDesktopName); + if (!o) + { + WARNINGKPILOT << "Can't find desktop file for conduit " + << fDesktopName + << endl; + addSyncLogEntry(i18n("Could not find conduit %1.").arg(fDesktopName)); + return false; + } + + + // load the lib + fLibraryName = o->library(); + DEBUGKPILOT << fname + << ": Loading desktop " + << fDesktopName + << " with lib " + << fLibraryName + << endl; + + KLibrary *library = KLibLoader::self()->library( + QFile::encodeName(fLibraryName)); + if (!library) + { + WARNINGKPILOT << "Can't load library " + << fLibraryName + << " - " + << KLibLoader::self()->lastErrorMessage() + << endl; + addSyncLogEntry(i18n("Could not load conduit %1.").arg(fDesktopName)); + return false; + } + + unsigned long version = PluginUtility::pluginVersion(library); + if ( Pilot::PLUGIN_API != version ) + { + WARNINGKPILOT << "Library " + << fLibraryName + << " has version " + << version + << endl; + addSyncLogEntry(i18n("Conduit %1 has wrong version (%2).").arg(fDesktopName).arg(version)); + return false; + } + + KLibFactory *factory = library->factory(); + if (!factory) + { + WARNINGKPILOT << "Can't find factory in library " + << fLibraryName + << endl; + addSyncLogEntry(i18n("Could not initialize conduit %1.").arg(fDesktopName)); + return false; + } + + QStringList l = syncMode().list(); + + DEBUGKPILOT << fname << ": Flags: " << syncMode().name() << endl; + + QObject *object = factory->create(fHandle,name(),"SyncAction",l); + + if (!object) + { + WARNINGKPILOT << "Can't create SyncAction." << endl; + addSyncLogEntry(i18n("Could not create conduit %1.").arg(fDesktopName)); + return false; + } + + fConduit = dynamic_cast<ConduitAction *>(object); + + if (!fConduit) + { + WARNINGKPILOT << "Can't cast to ConduitAction." << endl; + addSyncLogEntry(i18n("Could not create conduit %1.").arg(fDesktopName)); + return false; + } + + addSyncLogEntry(i18n("[Conduit %1]").arg(fDesktopName)); + + // Handle the syncDone signal properly & unload the conduit. + QObject::connect(fConduit,SIGNAL(syncDone(SyncAction *)), + this,SLOT(execDone(SyncAction *))); + // Proxy all the log and error messages. + QObject::connect(fConduit,SIGNAL(logMessage(const QString &)), + this,SIGNAL(logMessage(const QString &))); + QObject::connect(fConduit,SIGNAL(logError(const QString &)), + this,SIGNAL(logError(const QString &))); + QObject::connect(fConduit,SIGNAL(logProgress(const QString &,int)), + this,SIGNAL(logProgress(const QString &,int))); + + QTimer::singleShot(0,fConduit,SLOT(execConduit())); + return true; +} + +void ConduitProxy::execDone(SyncAction *p) +{ + FUNCTIONSETUP; + + if (p!=fConduit) + { + WARNINGKPILOT << "Unknown conduit @" + << (void *) p + << " finished." + << endl; + emit syncDone(this); + return; + } + + // give our worker a chance to sanity check the results... + fConduit->finished(); + + addSyncLogEntry(CSL1("\n"),false); // Put bits of the conduit logs on separate lines + + KPILOT_DELETE(p); + + emit syncDone(this); +} + + +namespace PluginUtility +{ + +QString findArgument(const QStringList &a, const QString &arg) +{ + FUNCTIONSETUP; + + QString search; + + if (arg.startsWith( CSL1("--") )) + { + search = arg; + } + else + { + search = CSL1("--") + arg; + } + search.append( CSL1("=") ); + + + QStringList::ConstIterator end = a.end(); + for (QStringList::ConstIterator i = a.begin(); i != end; ++i) + { + if ((*i).startsWith( search )) + { + QString s = (*i).mid(search.length()); + return s; + } + } + + return QString::null; +} + +/* static */ bool isRunning(const QCString &n) +{ + DCOPClient *dcop = KApplication::kApplication()->dcopClient(); + QCStringList apps = dcop->registeredApplications(); + return apps.contains(n); +} + + +/* static */ unsigned long pluginVersion(const KLibrary *lib) +{ + QString symbol = CSL1("version_"); + symbol.append(lib->name()); + + if (!lib->hasSymbol(symbol.latin1())) return 0; + + unsigned long *p = (unsigned long *)(lib->symbol(symbol.latin1())); + return *p; +} + + +/* static */ QString pluginVersionString(const KLibrary *lib) +{ + QString symbol= CSL1("id_"); + symbol.append(lib->name()); + + if (!lib->hasSymbol(symbol.latin1())) return QString::null; + + return QString::fromLatin1(*((const char **)(lib->symbol(symbol.latin1())))); +} + + +} + + +CUDCounter::CUDCounter(QString s) : + fC(0),fU(0),fD(0),fStart(0),fEnd(0),fType(s) +{ +} + +void CUDCounter::created(unsigned int c) +{ + fC += c; +} + +void CUDCounter::updated(unsigned int c) +{ + fU += c; +} + +void CUDCounter::deleted(unsigned int c) +{ + fD += c; +} + +void CUDCounter::setStartCount(unsigned int t) +{ + fStart = t; +} + +void CUDCounter::setEndCount(unsigned int t) +{ + fEnd = t; +} + +QString CUDCounter::moo() const +{ + QString result = fType + ": " + + i18n("Start: %1. End: %2. ").arg(fStart).arg(fEnd); + + if (fC > 0) result += i18n("%1 new. ").arg(fC); + if (fU > 0) result += i18n("%1 changed. ").arg(fU); + if (fD > 0) result += i18n("%1 deleted. ").arg(fD); + + if ( (fC+fU+fD) <= 0) result += i18n("No changes made. "); + + return result; +} + + diff --git a/kpilot/lib/plugin.h b/kpilot/lib/plugin.h new file mode 100644 index 000000000..b825b899f --- /dev/null +++ b/kpilot/lib/plugin.h @@ -0,0 +1,476 @@ +#ifndef _KPILOT_PLUGIN_H +#define _KPILOT_PLUGIN_H +/* KPilot +** +** Copyright (C) 2001 by Dan Pilone +** Copyright (C) 2002-2004,2006 Adriaan de Groot <groot@kde.org> +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qstringlist.h> + +#include "syncAction.h" + +/** @file +* This file defines the base class of all KPilot conduit plugins configuration +* dialogs. This is necessary so that we have a fixed API to talk to from +* inside KPilot. +* +* The factories used by KPilot plugins are also documented here. +*/ + + +class QTabWidget; +class KAboutData; +class KLibrary; + +class PilotDatabase; + +namespace Pilot +{ + /** + * As the API for conduits may change in the course of time, + * identify them and refuse to load incompatible API versions. + * Bump this number every release to the current YYYYMMDD + * value. + */ + static const unsigned int PLUGIN_API = 20061118; +} + +/** +* ConduitConfigBase is for configuration purposes. +* +* ConduitConfigBase: this is an object (with a widget!) that is embedded +* in a dialog. This is the currently preferred form for configuration, +* and it's what is used in the KPilot conduit configuration dialog. +* The factory is asked for a "ConduitConfigBase" object. +* +* NB. The reason that this is a QObject which needs to create a +* separate widget - instead of a QWidget subclass - has to do with +* layouting. If you make the widget with designer then the easiest +* thing to do is to use a grid layout there. Making ConduitConfigBase +* a QWidget subclass would require an additional layout here, which +* seems a little foolish. +* +*/ +class KDE_EXPORT ConduitConfigBase : public QObject +{ +Q_OBJECT +public: + /** + * Constructor. Creates a conduit configuration support object + * with the given parent @p parent and name (optional) @p n. + */ + ConduitConfigBase(QWidget *parent=0L, const char *n=0L); + + /** Destructor. */ + virtual ~ConduitConfigBase(); + + /** + * This function is called to check whether the configuration + * of the conduit has changed -- and hence, whether the user + * needs to be prompted. By default, this just returns + * fModified, but you can do more complicated things. + */ + virtual bool isModified() const + { + return fModified; + } ; + + /** Accessor for the actual widget for the configuration. */ + QWidget *widget() const + { + return fWidget; + } ; + + /** + * Load or save the config widget's settings in the given + * KConfig object; leave the group unchanged. load() and + * commit() should both call unmodified() to indicate that + * the current settings match the on-disk ones. + */ + virtual void commit() = 0; + virtual void load() = 0; + /** + * Called when the object is to be hidden again and might + * need to save changed settings. Should prompt the user + * and call commit() if needed. Override this function only + * if you need a very different kind of prompt window. + * + * Returns false if the change is to be canceled. Returns + * true otherwise, whether or not the changes were saved. + */ + virtual bool maybeSave(); + + QString conduitName() const { return fConduitName; } ; + + /** + * This is the function that does the work of adding an about + * page to a tabwidget. It is made public and static so that + * it can be used elsewhere wherever tabwidgets appear. + * + * The about tab is created using aboutPage(). The new about + * widget is added to the tab widget @p w with the heading + * "About". + * + * @param w The tab widget to which the about page is added. + * @param data The KAboutData that is used. + * + */ + static void addAboutPage(QTabWidget *w, + KAboutData *data=0L); + + /** + * This creates the actual about widget. Again, public & static so + * you can slap in an about widget wherever. + * + * An about widget is created that shows the contributors to + * the application, along with copyright information and the + * application's icon. This widget can be used pretty much + * anywhere. Copied from KAboutDialog, mostly. + * + * @param parent The widget that holds the about widget. + * @param data The KAboutData that is used to populate the widget. + */ + static QWidget *aboutPage(QWidget *parent, KAboutData *data=0L); + +protected: + /** + * This function provides the string for the prompt used + * in maybeSave(). Override it to change the text. + */ + virtual QString maybeSaveText() const; + + void unmodified() { fModified=false; } ; + + bool fModified; + QWidget *fWidget; + QString fConduitName; + + +protected slots: + void modified(); +signals: + void changed(bool); + +} ; + + +/** +* Create-Update-Delete tracking of the plugin, +* used for reporting purposes (in a consistent manner). The intent +* is that this class is used by the conduit as it is syncing data. +* For this to be useful (and be used properly), the conduit needs +* to tell us how many creates, updates, and deletes it has made to +* a data store (PC or HH). It also needs to tell us how many +* records it started with and how many records it has at the +* conclusion of its processing. Using this information, we can +* report on it consistently as well as analyze the activity taken +* by the conduit and offer rollback functionality if we think the +* conduit has behaved improperly. +*/ +class KDE_EXPORT CUDCounter +{ +public: + /** Create new counter initialized to 0, and be told what + * kind of CUD we're counting (PC or Handheld, etc.) */ + CUDCounter(QString s); + + /** Track the creation of @p c items */ + void created(unsigned int c=1); + /** Track updates to @p u items */ + void updated(unsigned int u=1); + /** Track the destruction of @p d items */ + void deleted(unsigned int d=1); + /** How many @p t items did we start with? */ + void setStartCount(unsigned int t); + /** How many @p t items did we end with? */ + void setEndCount(unsigned int t); + + unsigned int countCreated() { return fC; } + unsigned int countUpdated() { return fU; } + unsigned int countDeleted() { return fD; } + unsigned int countStart() { return fStart; } + unsigned int countEnd() { return fEnd; } + + /** percentage of changes. unfortunately, we have to rely on our + * developers (hi, self!) to correctly set total number of records + * conduits start with, so add a little protection... + */ + unsigned int percentCreated() { return (fEnd > 0 ? fC/fEnd : 0); } + unsigned int percentUpdated() { return (fEnd > 0 ? fU/fEnd : 0); } + unsigned int percentDeleted() { return (fStart > 0 ? fD/fStart : 0); } + + /** Measurement Of Objects -- report numbers of + * objects created, updated, deleted. This + * string is already i18n()ed. + */ + QString moo() const; + + /** Type of counter(Handheld or PC). This string is already + * i18n()ed. + */ + QString type() const { return fType; } +private: + /** keep track of Creates, Updates, Deletes, and Total + * number of records so we can detect abnormal behavior and + * hopefully prevent data loss. + */ + unsigned int fC,fU,fD,fStart,fEnd; + + /** What kind of CUD are we keeping track of so we can + * moo() it out later? (PC, Handheld, etc.) + */ + QString fType; +} ; + + +/** +* The SyncActions created by the factory should obey at least +* the argument test, indicating a dry run. The device link is +* the link where the sync should run -- don't get the pilotPort() +* until the sync runs! +* +* setConfig() will be called before the sync starts so that the +* conduit can read/write metadata and local settings. +*/ + +class KDE_EXPORT ConduitAction : public SyncAction +{ +Q_OBJECT + +public: + ConduitAction(KPilotLink *, + const char *name=0L, + const QStringList &args = QStringList()); + virtual ~ConduitAction(); + + /** ConduitAction is done doing work. Allow it to sanity-check the + * results + */ + void finished(); + + QString conduitName() const { return fConduitName; } ; + + /** Retrieve the sync mode set for this action. */ + const SyncMode &syncMode() const { return fSyncDirection; }; + + /** + * A full sync happens for eFullSync, eCopyPCToHH and eCopyHHToPC. It + * completely ignores all modified flags and walks through all records + * in the database. + */ + bool isFullSync() const + { + return fFirstSync || fSyncDirection.isFullSync() ; + } + + /** + * A first sync (i.e. database newly fetched from the handheld ) + * does not check for deleted records, but understands them as + * added on the other side. The flag is set by the conduits + * when opening the local database, or the calendar/addressbook + * (if it is empty). This also implies a full sync. + */ + bool isFirstSync() const + { + return fFirstSync || fSyncDirection.isFirstSync() ; + } + +protected: + /** Retrieve the conflict resolution setting for this action. */ + ConflictResolution getConflictResolution() const + { return fConflictResolution; }; + + /** Try to change the sync mode from what it is now to the mode @p m. + * This may fail (ie. changing a backup to a restore is not kosher) and + * changeSync() will return false then. + */ + bool changeSync(SyncMode::Mode m); + + // Set the conflict resolution, except if the resolution + // form is UseGlobalSetting, in which case nothing changes + // (assumes then that the resolution form is already set + // according to that global setting). + // + void setConflictResolution(ConflictResolution res) + { + if (SyncAction::eUseGlobalSetting != res) + fConflictResolution=res; + } + + void setFirstSync(bool first) { fFirstSync=first; } ; + + PilotDatabase *fDatabase; + PilotDatabase *fLocalDatabase; // Guaranteed to be a PilotLocalDatabase + + /** + * Open both the local copy of database @p dbName + * and the version on the Pilot. Return true only + * if both opens succeed. If the local copy of the database + * does not exist, it is retrieved from the handheld. In this + * case, retrieved is set to true, otherwise it is left alone + * (i.e. retains its value and is not explicitly set to false). + * + * @param dbName database name to open. + * @param retrieved indicator whether the database had to be loaded + * from the handheld. + */ + bool openDatabases(const QString &dbName, bool*retrieved=0L); + + /** + * Name of the conduit; might be changed by subclasses. Should + * normally be set in the constructor. + */ + QString fConduitName; + + /** Every plugin has 2 CUDCounters--one for keeping track of + * changes made to PC data and one for keeping track of Palm data. */ + CUDCounter *fCtrHH; + CUDCounter *fCtrPC; + +private: + SyncMode fSyncDirection; + ConflictResolution fConflictResolution; + + bool fFirstSync; +} ; + +/** +* The ConduitProxy action delays loading the plugin for a conduit until the conduit +* actually executes; the proxy then loads the file, creates a SyncAction for the conduit +* and runs that. Once the conduit has finished, the proxy unloads everything +* and emits syncDone(). +*/ +class ConduitProxy : public ConduitAction +{ +Q_OBJECT + +public: + ConduitProxy(KPilotLink *, + const QString &desktopName, + const SyncAction::SyncMode &m); + +protected: + virtual bool exec(); +protected slots: + void execDone(SyncAction *); + +protected: + QString fDesktopName; + QString fLibraryName; + ConduitAction *fConduit; +} ; + +/** A namespace containing only static helper methods. */ +namespace PluginUtility +{ + /** Searches the argument list for --foo=bar and returns bar, QString::null if not found. + * Don't include the -- in the argname. */ + QString findArgument(const QStringList &a, const QString argname); + + /** + * This function attempts to detect whether or not the given + * application is running. If it is, true is returned, otherwise + * false. + * + * The current approach is to ask the DCOP server if the application + * has registered. + */ + bool isRunning(const QCString &appName); + + /** + * Check a given library for its version, returning 0 if no + * version symbol is found. + */ + unsigned long pluginVersion(const KLibrary *); + QString pluginVersionString(const KLibrary *); +} + +/** +* All KPilot conduits should subclass KLibFactory like this. +* +* Boilerplate for inheritance: +* +* <pre> +* class KPilotPlugin : public KLibFactory +* { +* Q_OBJECT +* +* public: +* KPilotPlugin(QObject * = 0L,const char * = 0L) ; +* virtual ~KPilotPlugin(); +* </pre> +* +* You don't @em have to provide about information for the plugin, +* but it's useful, particularly for the about box in a conduit. +* +* +* <pre> +* static KAboutData *about() { return fAbout; } ; +* </pre> +* +* +* This is what it's all about: creating objects for the plugin. +* One classname that @em must be supported is ConduitConfig, +* which is defined above. The other is SyncAction. +* +* +* <pre> +* protected: +* virtual QObject* createObject( QObject* parent = 0, +* const char* name = 0, +* const char* classname = "QObject", +* const QStringList &args = QStringList() ); +* </pre> +* +* More boilerplate, and support for an instance and about data, used +* by about() above. +* +* <pre> +* KInstance *fInstance; +* static KAboutData *fAbout; +* } ; +* </pre> +* +* +* +* The implementation of a conduit needs an init_conduit_name() function, +* just like any KLibLoader library that uses factories. +* +* The createObject() function needs to support at least two creation +* calls: "ConduitConfigBase" and "SyncAction". +* "ConduitConfigBase" should return a subclass of ConduitConfigBase, +* "SyncAction" a subclass of SyncAction. +* +* Finally, a conduit should have a symbol version_conduit_name, +* that returns a long; much like the init_conduit_name() function. This +* should return the version of the plugin API (KPILOT_PLUGIN_VERSION) +* the conduit was compiled against. Additionally, a plugin may have a +* id_conduit_name, which should be a const char *. +* +*/ + +#endif diff --git a/kpilot/lib/pluginfactory.h b/kpilot/lib/pluginfactory.h new file mode 100644 index 000000000..8eecc5584 --- /dev/null +++ b/kpilot/lib/pluginfactory.h @@ -0,0 +1,98 @@ +#ifndef _KPILOT_PLUGINFACTORY_H +#define _KPILOT_PLUGINFACTORY_H +/* KPilot +** +** Copyright (C) 2005-2006 by Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qwidget.h> + +#include <kdebug.h> +#include <klibloader.h> + +#include "options.h" + +/** @file Defines a template class for factories for KPilot's conduits. */ + +class KPilotLink; + + + +/** Template class that defines a conduit's factory. */ + +template <class Widget, class Action> class ConduitFactory : public KLibFactory +{ +public: + ConduitFactory(QObject *parent = 0, const char *name = 0) : + KLibFactory(parent,name) + { fInstance = new KInstance(name); } ; + virtual ~ConduitFactory() + { delete fInstance; } ; + +protected: + virtual QObject *createObject( + QObject* parent = 0, + const char* name = 0, + const char* classname = "QObject", + const QStringList &args = QStringList() ) + { + if (qstrcmp(classname,"ConduitConfigBase")==0) + { + QWidget *w = dynamic_cast<QWidget *>(parent); + if (w) return new Widget(w,name); + else + { + WARNINGKPILOT << "Could not cast parent to widget." << endl; + return 0L; + } + } + + if (qstrcmp(classname,"SyncAction")==0) + { + KPilotLink *d = 0L; + if (parent) d = dynamic_cast<KPilotLink *>(parent); + + if (d || !parent) + { + if (!parent) + { + kdDebug() << k_funcinfo << ": Using NULL device." << endl; + } + return new Action(d,name,args); + } + else + { + WARNINGKPILOT << "Could not cast parent to KPilotLink" << endl; + return 0L; + } + } + return 0L; + } + + KInstance *fInstance; +} ; + +#endif + diff --git a/kpilot/lib/recordConduit.cc b/kpilot/lib/recordConduit.cc new file mode 100644 index 000000000..f11b3b573 --- /dev/null +++ b/kpilot/lib/recordConduit.cc @@ -0,0 +1,1145 @@ +/* KPilot +** +** Copyright (C) 2004 by Reinhold Kainhofer +** Based on the addressbook conduit: +** Copyright (C) 2000,2001 by Dan Pilone +** Copyright (C) 2000 Gregory Stern +** Copyright (C) 2002-2003 by Reinhold Kainhofer +** +** This conduit is the base class for all record-based conduits. +** all the sync logic is included in this class, and all child classes +** just have to implement some specific copying and conflict resolution +** methods. +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org. +*/ + + + +#include "options.h" + +#include <qtimer.h> +#include <qfile.h> + +#include "pilotAppCategory.h" +#include "pilotSerialDatabase.h" +#include "pilotLocalDatabase.h" +#include "recordConduit.h" + + +// Something to allow us to check what revision +// the modules are that make up a binary distribution. +// +// +extern "C" +{ +long version_record_conduit = Pilot::PLUGIN_API; +} + + +/* virtual */ bool RecordConduitBase::exec() +{ + FUNCTIONSETUP; + fState = Initialize; + + setFirstSync(false); + + bool retrieved = false; + if (!openDatabases( fDBName, &retrieved)) + { + emit logError(i18n("Unable to open the %1 database on the handheld.").arg( fDBName ) ); + return false; + } + if (retrieved) setFirstSync(true); + + if (isFirstSync()) fIDList=fDatabase->idList(); + else fIDList=fDatabase->modifiedIDList(); + fIDListIterator = fIDList.begin(); + + fTimer = new QTimer(this); + connect(fTimer,SIGNAL(timeout()),this,SLOT(process())); + fTimer->start(0,false); // Fire as often as possible to prompt processing + return true; +} + +/* virtual */ void RecordConduitBase::process() +{ + FUNCTIONSETUP; + SyncProgress p = Error; + +#ifdef DEBUG + DEBUGKPILOT << fname << ": From state " << name(fState) << endl; +#endif + + switch(fState) + { + case Initialize : + p = loadPC(); + break; + case PalmToPC : + p = palmRecToPC(); + break; + case PCToPalm : + p = pcRecToPalm(); + break; + case Cleanup : + p = cleanup(); + break; + } + +#ifdef DEBUG + DEBUGKPILOT << fname << ": Step returned " << name(p) << endl; +#endif + + switch(p) + { + case Error : + fTimer->stop(); + delayDone(); + return; + case NotDone : + // Return so we get called again. + return; + case Done : + // Get on with it. + break; + } + +#ifdef DEBUG + DEBUGKPILOT << fname << ": Step is done, moving to next state." << endl; +#endif + + // Here the previous call was done. + switch(fState) + { + case Initialize : + switch (syncMode().mode()) + { + case SyncMode::eRestore : + case SyncMode::eCopyPCToHH : /* These two don't copy Palm records to the PC */ + fState = PCToPalm; + break; + default : + fState = PalmToPC; + } + break; + case PalmToPC : + switch (syncMode().mode()) + { + case SyncMode::eBackup : + case SyncMode::eCopyHHToPC : /* These modes don't copy PC records back */ + fState = Cleanup; + break; + default : + fState = PCToPalm; + } + break; + case PCToPalm : + fState = Cleanup; + break; + case Cleanup : + fTimer->stop(); + delayDone(); + // No change in state, timer stopped and we're done. + break; + } + +#ifdef DEBUG + DEBUGKPILOT << fname << ": Next state is " << name(fState) << endl; +#endif + +} + + +QString RecordConduitBase::name(RecordConduitBase::SyncProgress s) +{ + switch(s) + { + case RecordConduitBase::NotDone: + return CSL1("NotDone"); + case RecordConduitBase::Done: + return CSL1("Done"); + case RecordConduitBase::Error: + return CSL1("Error"); + } +} + + +QString RecordConduitBase::name(RecordConduitBase::States s) +{ + switch(s) + { + case RecordConduitBase::Initialize: + return CSL1("Initialize"); + case RecordConduitBase::PalmToPC: + return CSL1("Handheld-to-PC"); + case RecordConduitBase::PCToPalm: + return CSL1("PC-to-Handheld"); + case RecordConduitBase::Cleanup: + return CSL1("Cleanup"); + } +} + + +#if 0 +/** make that entry on the pc archived (i.e. deleted on the handheld, + * while present on the pc, but not synced to the handheld */ +bool RecordConduit::PCData::makeArchived( RecordConduit::PCEntry *pcEntry ) +{ + if ( pcEntry ) { + pcEntry->makeArchived(); + setChanged( true ); + return true; + } else return false; +} + + +/* Builds the map which links record ids to uid's of PCEntry. This is the slow implementation, + * that should always work. subclasses should reimplement it to speed things up. +*/ +bool RecordConduit::PCData::mapContactsToPilot( QMap<recordid_t,QString> &idContactMap ) +{ + FUNCTIONSETUP; + + idContactMap.clear(); + + Iterator it = begin(); + PCEntry *ent; + while ( !atEnd( it ) ) { + ent = *it; + recordid_t id( ent->recid() ); + if ( id != 0 ) { + idContactMap.insert( id, ent->uid() ); + } + ++it; + } +#ifdef DEBUG + DEBUGKPILOT << fname << ": Loaded " << idContactMap.size() << + " Entries on the pc and mapped them to records on the handheld. " << endl; +#endif + return true; +} + + + +/********************************************************************* + C O N S T R U C T O R + *********************************************************************/ + + + +bool RecordConduit::mArchiveDeleted = false; + +RecordConduit::RecordConduit(QString name, KPilotDeviceLink * o, const char *n, const QStringList & a): + ConduitAction(o, n, a), + mPCData(0), mPalmIndex(0), + mEntryMap(), mSyncedIds(), mAllIds() +{ + FUNCTIONSETUP; + fConduitName = name; +} + + + +RecordConduit::~RecordConduit() +{ + if ( mPCData ) KPILOT_DELETE(mPCData); +} + + + + + + +/********************************************************************* + S Y N C S T R U C T U R E + *********************************************************************/ + + + +/* virtual */ bool RecordConduit::exec() +{ + FUNCTIONSETUP; + + if ( !_prepare() ) return false; + + fFirstSync = false; + // Database names probably in latin1. + if( !openDatabases( dbName(), &fFirstSync ) ) + { + emit logError(i18n("Unable to open the %1 database on the handheld.").arg( dbName() ) ); + return false; + } + _getAppInfo(); + if( !mPCData->loadData() ) + { + emit logError( i18n("Unable to open %1.").arg( mPCData->description() ) ); + return false; + } + // get the addresseMap which maps Pilot unique record(address) id's to + // a Abbrowser Addressee; allows for easy lookup and comparisons + if ( mPCData->isEmpty() ) + fFirstSync = true; + else + mPCData->mapContactsToPilot( mEntryMap ); + fFirstSync = fFirstSync || ( mPCData->isEmpty() ); + + // perform syncing from palm to abbrowser + // iterate through all records in palm pilot + mPalmIndex = 0; + +#ifdef DEBUG + DEBUGKPILOT << fname << ": fullsync=" << isFullSync() << ", firstSync=" << isFirstSync() << endl; + DEBUGKPILOT << fname << ": " + << "syncDirection=" << getSyncDirection() << ", " +// << "archive = " << AbbrowserSettings::archiveDeleted() + << endl; + DEBUGKPILOT << fname << ": conflictRes="<< getConflictResolution() << endl; +// DEBUGKPILOT << fname << ": PilotStreetHome=" << AbbrowserSettings::pilotStreet() << ", PilotFaxHOme" << AbbrowserSettings::pilotFax() << endl; +#endif + + if ( !isFirstSync() ) + mAllIds=fDatabase->idList(); + + /* Note: + if eCopyPCToHH or eCopyHHToPC, first sync everything, then lookup + those entries on the receiving side that are not yet syncced and delete + them. Use slotDeleteUnsyncedPCRecords and slotDeleteUnsyncedHHRecords + for this, and no longer purge the whole addressbook before the sync to + prevent data loss in case of connection loss. */ + + QTimer::singleShot(0, this, SLOT(slotPalmRecToPC())); + + return true; +} + + + +void RecordConduit::slotPalmRecToPC() +{ + FUNCTIONSETUP; + PilotRecord *palmRec = 0L, *backupRec = 0L; + + if ( getSyncDirection() == SyncAction::eCopyPCToHH ) + { + mPCIter = mPCData->begin(); + QTimer::singleShot(0, this, SLOT(slotPCRecToPalm())); + return; + } + + if ( isFullSync() ) + palmRec = fDatabase->readRecordByIndex( mPalmIndex++ ); + else + palmRec = dynamic_cast <PilotSerialDatabase * >(fDatabase)->readNextModifiedRec(); + + if ( !palmRec ) + { + mPCIter = mPCData->begin(); + QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) ); + return; + } + + // already synced, so skip: + if ( mSyncedIds.contains( palmRec->id() ) ) + { + KPILOT_DELETE( palmRec ); + QTimer::singleShot( 0, this, SLOT( slotPalmRecToPC() ) ); + return; + } + + backupRec = fLocalDatabase->readRecordById( palmRec->id() ); + PilotRecord *compareRec = backupRec ? backupRec : palmRec; + PilotAppCategory *compareEntry = createPalmEntry( compareRec ); + PCEntry *pcEntry = findMatch( compareEntry ); + KPILOT_DELETE( compareEntry ); + + PilotAppCategory *backupEntry=0L; + if ( backupRec ) + backupEntry = createPalmEntry( backupRec ); + PilotAppCategory *palmEntry=0L; + if ( palmRec ) + palmEntry = createPalmEntry( palmRec ); + + syncEntry( pcEntry, backupEntry, palmEntry ); + + mSyncedIds.append( palmRec->id() ); + + KPILOT_DELETE( pcEntry ); + KPILOT_DELETE( palmEntry ); + KPILOT_DELETE( backupEntry ); + KPILOT_DELETE( palmRec ); + KPILOT_DELETE( backupRec ); + + QTimer::singleShot(0, this, SLOT(slotPalmRecToPC())); +} + + + +void RecordConduit::slotPCRecToPalm() +{ + FUNCTIONSETUP; + + if ( ( getSyncDirection()==SyncAction::eCopyHHToPC ) || + mPCData->atEnd( mPCIter ) ) + { + mPalmIndex = 0; + QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) ); + return; + } + + PilotRecord *backupRec=0L; + PCEntry *pcEntry = *mPCIter; + ++mPCIter; + + // If marked as archived, don't sync! + if ( isArchived( pcEntry ) ) + { +#ifdef DEBUG + DEBUGKPILOT << fname << ": address with id " << pcEntry->uid() << + " marked archived, so don't sync." << endl; +#endif + KPILOT_DELETE( pcEntry ); + QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) ); + return; + } + + recordid_t recID( pcEntry->recid() ); + if ( recID == 0 ) + { + // it's a new item(no record ID and not inserted by the Palm -> PC sync), so add it + syncEntry( pcEntry, 0L, 0L ); + KPILOT_DELETE( pcEntry ); + QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) ); + return; + } + + // look into the list of already synced record ids to see if the PCEntry hasn't already been synced + if ( mSyncedIds.contains( recID ) ) + { +#ifdef DEBUG + DEBUGKPILOT << ": address with id " << recID << " already synced." << endl; +#endif + KPILOT_DELETE( pcEntry ); + QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) ); + return; + } + + + backupRec = fLocalDatabase->readRecordById( recID ); + // only update if no backup record or the backup record is not equal to the PCEntry + + PilotAppCategory*backupEntry=0L; + if ( backupRec ) + backupEntry = createPalmEntry( backupRec ); + if( !backupRec || isFirstSync() || !_equal( backupEntry, pcEntry ) ) + { + PilotRecord *palmRec = fDatabase->readRecordById( recID ); + PilotAppCategory *palmEntry=0L; + if (palmRec) + palmEntry = createPalmEntry( palmRec ); + syncEntry( pcEntry, backupEntry, palmEntry ); + // update the id just in case it changed + if ( palmRec ) + recID = palmRec->id(); + KPILOT_DELETE( palmRec ); + KPILOT_DELETE( palmEntry ); + } + + KPILOT_DELETE( pcEntry ); + KPILOT_DELETE( backupEntry ); + KPILOT_DELETE( backupRec ); + mSyncedIds.append( recID ); + + // done with the sync process, go on with the next one: + QTimer::singleShot( 0, this, SLOT( slotPCRecToPalm() ) ); +} + + + +void RecordConduit::slotDeletedRecord() +{ + FUNCTIONSETUP; + + PilotRecord *backupRec = fLocalDatabase->readRecordByIndex( mPalmIndex++ ); + if( !backupRec || isFirstSync() ) + { + KPILOT_DELETE(backupRec); + QTimer::singleShot( 0, this, SLOT( slotDeleteUnsyncedPCRecords() ) ); + return; + } + + // already synced, so skip this record: + if ( mSyncedIds.contains( backupRec->id() ) ) + { + KPILOT_DELETE( backupRec ); + QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) ); + return; + } + + QString uid = mEntryMap[ backupRec->id() ]; + PCEntry *pcEntry = mPCData->findByUid( uid ); + PilotRecord *palmRec = fDatabase->readRecordById( backupRec->id() ); + PilotAppCategory *backupEntry = 0L; + if (backupRec) + backupEntry = createPalmEntry( backupRec ); + PilotAppCategory*palmEntry=0L; + if (palmRec) + palmEntry = createPalmEntry( palmRec ); + + mSyncedIds.append( backupRec->id() ); + syncEntry( pcEntry, backupEntry, palmEntry ); + + KPILOT_DELETE( pcEntry ); + KPILOT_DELETE( palmEntry ); + KPILOT_DELETE( backupEntry ); + KPILOT_DELETE( palmRec ); + KPILOT_DELETE( backupRec ); + QTimer::singleShot( 0, this, SLOT( slotDeletedRecord() ) ); +} + + + +void RecordConduit::slotDeleteUnsyncedPCRecords() +{ + FUNCTIONSETUP; + if ( getSyncDirection() == SyncAction::eCopyHHToPC ) + { + QStringList uids; + RecordIDList::iterator it; + QString uid; + for ( it = mSyncedIds.begin(); it != mSyncedIds.end(); ++it) + { + uid = mEntryMap[ *it ]; + if ( !uid.isEmpty() ) uids.append( uid ); + } + // TODO: Does this speed up anything? + // qHeapSort( uids ); + const QStringList alluids( mPCData->uids() ); + QStringList::ConstIterator uidit; + for ( uidit = alluids.constBegin(); uidit != alluids.constEnd(); ++uidit ) + { + if ( !uids.contains( *uidit ) ) + { +#ifdef DEBUG + DEBUGKPILOT << "Deleting PCEntry with uid " << (*uidit) << " from PC (is not on HH, and syncing with HH->PC direction)" << endl; +#endif + mPCData->removeEntry( *uidit ); + } + } + } + QTimer::singleShot(0, this, SLOT(slotDeleteUnsyncedHHRecords())); +} + + + +void RecordConduit::slotDeleteUnsyncedHHRecords() +{ + FUNCTIONSETUP; + if ( getSyncDirection() == SyncAction::eCopyPCToHH ) + { + RecordIDList ids = fDatabase->idList(); + RecordIDList::iterator it; + for ( it = ids.begin(); it != ids.end(); ++it ) + { + if ( !mSyncedIds.contains(*it) ) + { +#ifdef DEBUG + DEBUGKPILOT << "Deleting record with ID " << *it << " from handheld (is not on PC, and syncing with PC->HH direction)" << endl; +#endif + fDatabase->deleteRecord(*it); + fLocalDatabase->deleteRecord(*it); + } + } + } + QTimer::singleShot( 0, this, SLOT( slotCleanup() ) ); +} + + +void RecordConduit::slotCleanup() +{ + FUNCTIONSETUP; + + // Set the appInfoBlock, just in case the category labels changed + _setAppInfo(); + doPostSync(); + if(fDatabase) + { + fDatabase->resetSyncFlags(); + fDatabase->cleanup(); + } + if(fLocalDatabase) + { + fLocalDatabase->resetSyncFlags(); + fLocalDatabase->cleanup(); + } + KPILOT_DELETE( fDatabase ); + KPILOT_DELETE( fLocalDatabase ); + // TODO: do something if saving fails! + mPCData->saveData(); + mPCData->cleanup(); + emit syncDone(this); +} + + +/** Return the list of category names on the handheld + */ +const QStringList RecordConduit::categories() const +{ + QStringList cats; + for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) { + QString catName( category( j ) ); + if ( !catName.isEmpty() ) cats << catName; + } + return cats; +} +int RecordConduit::findFlags() const +{ + return eqFlagsAlmostAll; +} + + +bool RecordConduit::isDeleted( const PilotAppCategory *palmEntry ) +{ + if ( !palmEntry ) + return true; + if ( palmEntry->isDeleted() && !palmEntry->isArchived() ) + return true; + if ( palmEntry->isArchived() ) + return !archiveDeleted(); + return false; +} +bool RecordConduit::isArchived( const PilotAppCategory *palmEntry ) +{ + if ( palmEntry && palmEntry->isArchived() ) + return archiveDeleted(); + else + return false; +} + + + + +/********************************************************************* + L O A D I N G T H E D A T A + *********************************************************************/ + + + +bool RecordConduit::_prepare() +{ + FUNCTIONSETUP; + + readConfig(); + mSyncedIds.clear(); + mPCData = initializePCData(); + + return mPCData && doPrepare(); +} + + +void RecordConduit::_getAppInfo() +{ + FUNCTIONSETUP; + // get the address application header information + unsigned char *buffer = new unsigned char[Pilot::MAX_APPINFO_SIZE]; + int appLen=fDatabase->readAppBlock(buffer, Pilot::MAX_APPINFO_SIZE); + + doUnpackAppInfo( buffer, appLen ); + delete[] buffer; + buffer = 0; +} + +void RecordConduit::_setAppInfo() +{ + FUNCTIONSETUP; + // get the address application header information + int appLen = 0; + unsigned char *buffer = doPackAppInfo( &appLen ); + if ( buffer ) + { if (fDatabase) + fDatabase->writeAppBlock( buffer, appLen ); + if (fLocalDatabase) + fLocalDatabase->writeAppBlock( buffer, appLen ); + delete[] buffer; + } +} + + +int RecordConduit::compareStr( const QString & str1, const QString & str2 ) +{ +// FUNCTIONSETUP; + if ( str1.isEmpty() && str2.isEmpty() ) + return 0; + else + return str1.compare( str2 ); +} + + +/** + * _getCat returns the id of the category from the given categories list. + * If the address has no categories on the PC, QString::null is returned. + * If the current category exists in the list of cats, it is returned + * Otherwise the first cat in the list that exists on the HH is returned + * If none of the categories exists on the palm, QString::null is returned + */ +QString RecordConduit::getCatForHH( const QStringList cats, const QString curr ) const +{ + FUNCTIONSETUP; + if ( cats.size() < 1 ) + return QString::null; + if ( cats.contains( curr ) ) + return curr; + for ( QStringList::ConstIterator it = cats.begin(); it != cats.end(); ++it) + { + for ( unsigned int j = 0; j < Pilot::CATEGORY_COUNT; j++ ) + { + QString catnm( category( j ) ); + if ( !(*it).isEmpty() && ( (*it)==catnm ) ) + { + return catnm; + } + } + } + // If we have a free label, return the first possible cat + QString lastCat( category( Pilot::CATEGORY_COUNT-1 ) ); + return ( lastCat.isEmpty() ) ? ( cats.first() ) : ( QString::null ); +} + +void RecordConduit::setCategory(PCEntry * pcEntry, QString cat) +{ + if ( !cat.isEmpty() && cat!=category( 0 ) ) + pcEntry->insertCategory(cat); +} + + + + + + +/********************************************************************* + G E N E R A L S Y N C F U N C T I O N + These functions modify the Handheld and the addressbook + *********************************************************************/ + + + +bool RecordConduit::syncEntry( PCEntry *pcEntry, PilotAppCategory*backupEntry, + PilotAppCategory*palmEntry) +{ + FUNCTIONSETUP; + + if ( getSyncDirection() == SyncAction::eCopyPCToHH ) + { + if ( pcEntry->isEmpty() ) + { + return pcDeleteEntry( pcEntry, backupEntry, palmEntry ); + } + else + { + return pcCopyToPalm( pcEntry, backupEntry, palmEntry ); + } + } + + if ( getSyncDirection() == SyncAction::eCopyHHToPC ) + { + if (!palmEntry) + return pcDeleteEntry(pcEntry, backupEntry, palmEntry); + else + return palmCopyToPC(pcEntry, backupEntry, palmEntry); + } + + if ( !backupEntry || isFirstSync() ) + { + /* + Resolution matrix (0..does not exist, E..exists, D..deleted flag set, A..archived): + HH PC | Resolution + ------------------------------------------------------------ + 0 A | - + 0 E | PC -> HH, reset ID if not set correctly + D 0 | delete (error, should never occur!!!) + D E | CR (ERROR) + E/A 0 | HH -> PC + E/A E/A| merge/CR + */ + if ( !palmEntry && isArchived( pcEntry ) ) + { + return true; + } + else if ( !palmEntry && !pcEntry->isEmpty() ) + { + // PC->HH + bool res = pcCopyToPalm( pcEntry, 0L, 0L ); + return res; + } + else if ( !palmEntry && pcEntry->isEmpty() ) + { + // everything's empty -> ERROR + return false; + } + else if ( ( isDeleted( palmEntry ) || isArchived( palmEntry ) ) && pcEntry->isEmpty()) + { + if ( isArchived( palmEntry ) ) + return palmCopyToPC( pcEntry, 0L, palmEntry ); + else + // this happens if you add a record on the handheld and delete it again before you do the next sync + return pcDeleteEntry( pcEntry, 0L, palmEntry ); + } + else if ( ( isDeleted(palmEntry) || isArchived( palmEntry ) ) && !pcEntry->isEmpty() ) + { + // CR (ERROR) + return smartMergeEntry( pcEntry, 0L, palmEntry ); + } + else if ( pcEntry->isEmpty() ) + { + // HH->PC + return palmCopyToPC( pcEntry, 0L, palmEntry ); + } + else + { + // Conflict Resolution + return smartMergeEntry( pcEntry, 0L, palmEntry ); + } + } // !backupEntry + else + { + /* + Resolution matrix: + 1) if HH.(empty| (deleted &! archived) ) -> { if (PC==B) -> delete, else -> CR } + if HH.archived -> {if (PC==B) -> copyToPC, else -> CR } + if PC.empty -> { if (HH==B) -> delete, else -> CR } + if PC.archived -> {if (HH==B) -> delete on HH, else CR } + 2) if PC==HH -> { update B, update ID of PC if needed } + 3) if PC==B -> { HH!=PC, thus HH modified, so copy HH->PC } + if HH==B -> { PC!=HH, thus PC modified, so copy PC->HH } + 4) else: all three PCEntrys are different -> CR + */ + + if ( !palmEntry || isDeleted(palmEntry) ) + { + if ( _equal( backupEntry, pcEntry ) || pcEntry->isEmpty() ) + { + return pcDeleteEntry( pcEntry, backupEntry, 0L ); + } + else + { + return smartMergeEntry( pcEntry, backupEntry, 0L ); + } + } + else if ( pcEntry->isEmpty() ) + { + if (*palmEntry == *backupEntry) + { + return pcDeleteEntry( pcEntry, backupEntry, palmEntry ); + } + else + { + return smartMergeEntry( pcEntry, backupEntry, palmEntry ); + } + } + else if ( _equal( palmEntry, pcEntry ) ) + { + // update Backup, update ID of PC if neededd + return backupSaveEntry( palmEntry ); + } + else if ( _equal( backupEntry, pcEntry ) ) + { +#ifdef DEBUG + DEBUGKPILOT << "Flags: " << palmEntry->getAttrib() << ", isDeleted=" << + isDeleted( palmEntry ) << ", isArchived=" << isArchived( palmEntry ) + << endl; +#endif + if ( isDeleted( palmEntry ) ) + { + return pcDeleteEntry( pcEntry, backupEntry, palmEntry ); + } + else + { + return palmCopyToPC( pcEntry, backupEntry, palmEntry ); + } + } + else if ( *palmEntry == *backupEntry ) + { + return pcCopyToPalm( pcEntry, backupEntry, palmEntry ); + } + else + { + // CR, since all are different + return smartMergeEntry( pcEntry, backupEntry, palmEntry ); + } + } // backupEntry + return false; +} + +bool RecordConduit::pcCopyToPalm( PCEntry *pcEntry, PilotAppCategory *backupEntry, + PilotAppCategory*palmEntry ) +{ + FUNCTIONSETUP; + + if ( pcEntry->isEmpty() ) return false; + PilotAppCategory *hhEntry = palmEntry; + bool hhEntryCreated = false; + if ( !hhEntry ) + { + hhEntry = createPalmEntry( 0 ); + hhEntryCreated=true; + } + _copy( hhEntry, pcEntry ); +#ifdef DEBUG + DEBUGKPILOT << "palmEntry->id=" << hhEntry->id() << ", pcEntry.ID=" << + pcEntry->uid() << endl; +#endif + + if( palmSaveEntry( hhEntry, pcEntry ) ) + { +#ifdef DEBUG + DEBUGKPILOT << "Entry palmEntry->id=" << + hhEntry->id() << "saved to palm, now updating pcEntry->uid()=" << pcEntry->uid() << endl; +#endif + pcSaveEntry( pcEntry, backupEntry, hhEntry ); + } + if ( hhEntryCreated ) KPILOT_DELETE( hhEntry ); + return true; +} + + + + +bool RecordConduit::palmCopyToPC( PCEntry *pcEntry, PilotAppCategory *backupEntry, + PilotAppCategory *palmEntry ) +{ + FUNCTIONSETUP; + if ( !palmEntry ) + { + return false; + } + _copy( pcEntry, palmEntry ); + pcSaveEntry( pcEntry, backupEntry, palmEntry ); + backupSaveEntry( palmEntry ); + return true; +} + + + +/********************************************************************* + l o w - l e v e l f u n c t i o n s f o r + adding / removing palm/pc records + *********************************************************************/ + + + +bool RecordConduit::palmSaveEntry( PilotAppCategory *palmEntry, PCEntry *pcEntry ) +{ + FUNCTIONSETUP; + +#ifdef DEBUG + DEBUGKPILOT << "Saving to pilot " << palmEntry->id() << endl; +#endif + + PilotRecord *pilotRec = palmEntry->pack(); + recordid_t pilotId = fDatabase->writeRecord(pilotRec); +#ifdef DEBUG + DEBUGKPILOT << "PilotRec nach writeRecord (" << pilotId << + ": ID=" << pilotRec->id() << endl; +#endif + fLocalDatabase->writeRecord( pilotRec ); + KPILOT_DELETE( pilotRec ); + + // pilotId == 0 if using local db, so don't overwrite the valid id + if ( pilotId != 0 ) + { + palmEntry->setID( pilotId ); + if ( !mSyncedIds.contains( pilotId ) ) + { + mSyncedIds.append( pilotId ); + } + } + + recordid_t hhId( pcEntry->recid() ); + if ( hhId != pilotId ) + { + pcEntry->setRecid( pilotId ); + return true; + } + + return false; +} + + + +bool RecordConduit::backupSaveEntry( PilotAppCategory *backup ) +{ + FUNCTIONSETUP; + if ( !backup ) return false; + + +#ifdef DEBUG +// showPilotAppCategory( backup ); +#endif + PilotRecord *pilotRec = backup->pack(); + fLocalDatabase->writeRecord( pilotRec ); + KPILOT_DELETE( pilotRec ); + return true; +} + + + +bool RecordConduit::pcSaveEntry( PCEntry *pcEntry, PilotAppCategory *, + PilotAppCategory * ) +{ + FUNCTIONSETUP; + +#ifdef DEBUG + DEBUGKPILOT << "Before _savepcEntry, pcEntry->uid()=" << + pcEntry->uid() << endl; +#endif + if ( pcEntry->recid() != 0 ) + { + mEntryMap.insert( pcEntry->recid(), pcEntry->uid() ); + } + + mPCData->updateEntry( pcEntry ); + return true; +} + + + +bool RecordConduit::pcDeleteEntry( PCEntry *pcEntry, PilotAppCategory *backupEntry, + PilotAppCategory *palmEntry ) +{ + FUNCTIONSETUP; + + if ( palmEntry ) + { + if ( !mSyncedIds.contains( palmEntry->id() ) ) + { + mSyncedIds.append(palmEntry->id()); + } + palmEntry->makeDeleted(); + PilotRecord *pilotRec = palmEntry->pack(); + pilotRec->setDeleted(); + mPalmIndex--; + fDatabase->writeRecord( pilotRec ); + fLocalDatabase->writeRecord( pilotRec ); + mSyncedIds.append( pilotRec->id() ); + KPILOT_DELETE( pilotRec ); + } + else if ( backupEntry ) + { + if ( !mSyncedIds.contains( backupEntry->id() ) ) + { + mSyncedIds.append( backupEntry->id() ); + } + backupEntry->makeDeleted(); + PilotRecord *pilotRec = backupEntry->pack(); + pilotRec->setDeleted(); + mPalmIndex--; + fLocalDatabase->writeRecord( pilotRec ); + mSyncedIds.append( pilotRec->id() ); + KPILOT_DELETE( pilotRec ); + } + if ( !pcEntry->isEmpty() ) + { +#ifdef DEBUG + DEBUGKPILOT << fname << " removing " << pcEntry->uid() << endl; +#endif + mPCData->removeEntry( pcEntry ); + } + return true; +} + + + +/********************************************************************* + C O P Y R E C O R D S + *********************************************************************/ + + + + + +/********************************************************************* + C O N F L I C T R E S O L U T I O N a n d M E R G I N G + *********************************************************************/ + + + + +// TODO: right now entries are equal if both first/last name and organization are +// equal. This rules out two entries for the same person(e.g. real home and weekend home) +// or two persons with the same name where you don't know the organization.!!! +RecordConduit::PCEntry *RecordConduit::findMatch( PilotAppCategory *palmEntry ) const +{ + FUNCTIONSETUP; + if ( !palmEntry ) + return 0; + + // TODO: also search with the pilotID + // first, use the pilotID to UID map to find the appropriate record + if( !isFirstSync() && ( palmEntry->id() > 0) ) + { + QString id( mEntryMap[palmEntry->id()] ); +#ifdef DEBUG + DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() << ", mapped to " << id << endl; +#endif + if( !id.isEmpty() ) + { + PCEntry *res = mPCData->findByUid( id ); + if ( !res && !res->isEmpty() ) return res; + KPILOT_DELETE( res ); +#ifdef DEBUG + DEBUGKPILOT << fname << ": PilotRecord has id " << palmEntry->id() << + ", but could not be found on the PC side" << endl; +#endif + } + } + + for ( PCData::Iterator iter = mPCData->begin(); !mPCData->atEnd( iter ); ++iter ) + { + PCEntry *abEntry = *iter; + recordid_t rid( abEntry->recid() ); + if ( rid>0 ) + { + if ( rid == palmEntry->id() ) + return abEntry;// yes, we found it + // skip this PCEntry, as it has a different corresponding address on the handheld + //if ( mAllIds.contains( rid ) ) continue; + } + + if ( _equal( palmEntry, abEntry, eqFlagsAlmostAll ) ) + { + return abEntry; + } + KPILOT_DELETE( abEntry ); + } +#ifdef DEBUG + DEBUGKPILOT << fname << ": Could not find any entry matching Palm record with id " << QString::number( palmEntry->id() ) << endl; +#endif + return 0; +} + +#endif + + + + +#include "recordConduit.moc" + diff --git a/kpilot/lib/recordConduit.h b/kpilot/lib/recordConduit.h new file mode 100644 index 000000000..d12ceef2e --- /dev/null +++ b/kpilot/lib/recordConduit.h @@ -0,0 +1,181 @@ +#ifndef _KPILOT_RECORDCONDUIT_H +#define _KPILOT_RECORDCONDUIT_H +/* record-conduit.h KPilot +** +** Copyright (C) 2005 by Adriaan de Groot +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <qtimer.h> +#include <klocale.h> + +#include "plugin.h" +#include "pilot.h" +#include "pilotDatabase.h" +#include "kpilotdevicelink.h" + +class KPilotDeviceLink; + +/** @file +* +* This file defines a generic syncing framework for Palm Pilot oriented data. +* It is a lot like KitchenSync's Syncees and such. Basically, we define a +* generic container for data on the PC side and give that container an API +* for searching for a specific handheld record. Syncing consists of iterating +* through the handheld's records and looking up the PC data for each record, +* and then syncing. +* +*/ + +/** An intermediate class that introduces the slots we need for our sync +* implementation. This is here _only_ because mixing moc with template +* classes sounds really scary. +*/ + +class RecordConduitBase : public ConduitAction +{ +Q_OBJECT +public: + /** Constructor. The QStringList @p a sets flags for the ConduitAction. + */ + RecordConduitBase(KPilotDeviceLink *o, + const char *n, + const QStringList a = QStringList()) : + ConduitAction(o,n,a), + fTimer(0L) + { + } ; + /** Destructor. */ + virtual ~RecordConduitBase() + { + // delete fTimer; // Timer is a child object + } ; + + /** Return values for the processing functions. Each should return + * NotDone if it needs to be called again (e.g. to process another record), + * Done if it is finished and something else should be done, and + * Error if the sync cannot be completed. + */ + enum SyncProgress { NotDone=0, Done=1, Error=2 } ; + + /** Returns a human-readable name for the progress indicator @p s */ + static QString name(SyncProgress s); + + /** State of the conduit's sync. This is changed by process(). */ + enum States { Initialize, PalmToPC, PCToPalm, Cleanup } ; + + static QString name(States s); + +protected: + /** Function called at the beginning of a sync to load data from the PC. + * @return Done when the load has finished. + * @see process + */ + virtual SyncProgress loadPC() = 0; + + /** Function called repeatedly to fetch the next modified entry from the Palm and + * sync it with the PC by looking up the record, and calling the syncer for it. + * + * @return Dome when there are no more modified records on the Palm + * @see process() + */ + virtual SyncProgress palmRecToPC() = 0; + + /** Function called repeatedly to fetch the next modified entry from the PC and + * sync it with the Palm by looking up the record and calling the syncer for it. + * + * @return Done when there are no more modified records on the PC + * @see process() + */ + virtual SyncProgress pcRecToPalm() = 0; + + /** Function called at the end of this conduit's sync, which should reset DB flags + * and write changed config data out to disk. + * + * @return Done when the cleanup is complete. + * @see process() + */ + virtual SyncProgress cleanup() = 0; + +protected slots: + /** Slot used for the implementation of a state machine: calls each of the + * relevant other slots (above) as needed until they return true. + */ + void process(); + +protected: + virtual bool exec(); + +private: + /** Timer to signal the process() slot. Used to keep the UI responsive. */ + QTimer *fTimer; + + States fState; + + Pilot::RecordIDList fIDList; + Pilot::RecordIDList::Iterator fIDListIterator; + + QString fDBName; +} ; + +template <class PCEntry, class PCContainer, class HHEntry, class HHAppInfo, class Syncer> +class RecordConduit : public RecordConduitBase +{ +public: + /** Construct a record conduit on a given device link. */ + RecordConduit( + KPilotDeviceLink *o /**< Connection to HH */, + const char *n /**< Name for QObject */, + const QStringList a = QStringList() /**< Flags */) : + RecordConduitBase(o,n,a) + { + } ; + virtual ~RecordConduit() + { + } ; + + virtual SyncProgress loadPC() + { + return Done; + } ; + + virtual SyncProgress palmRecToPC() + { + return Done; + } + + virtual SyncProgress pcRecToPalm() + { + return Done; + } + + virtual SyncProgress cleanup() + { + return Done; + } +} ; + + +#endif + diff --git a/kpilot/lib/syncAction.cc b/kpilot/lib/syncAction.cc new file mode 100644 index 000000000..818503807 --- /dev/null +++ b/kpilot/lib/syncAction.cc @@ -0,0 +1,512 @@ +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2001 by Waldo Bastian (code in questionYesNo) +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include "options.h" + +#include <time.h> + +#include <pi-socket.h> +#include <pi-dlp.h> + +#include <qtimer.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qcheckbox.h> +#include <qlabel.h> +#include <qmessagebox.h> +#include <qdir.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qtl.h> +#include <qstyle.h> + +#include <kdialogbase.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kconfig.h> +#include <kmessagebox.h> + +#include "syncAction.moc" +#include "kpilotlibSettings.h" + +SyncAction::SyncAction(KPilotLink *p, + const char *name) : + QObject(p, name), + fHandle(p), + fParent(0L) +{ + FUNCTIONSETUP; +} + +SyncAction::SyncAction(KPilotLink *p, + QWidget * visibleparent, + const char *name) : + QObject(p, name), + fHandle(p), + fParent(visibleparent) +{ + FUNCTIONSETUP; +} + +SyncAction::~SyncAction() +{ +} + +/* virtual */ QString SyncAction::statusString() const +{ + FUNCTIONSETUP; + QString s = CSL1("status="); + + s.append(QString::number(status())); + return s; +} + +/* slot */ void SyncAction::execConduit() +{ + FUNCTIONSETUP; + + DEBUGKPILOT << fname << ": Exec " << name() << endl; + + bool r = this->exec(); + + DEBUGKPILOT << fname << ": Exec " << name() + << (r ? " is running" : " failed to start") << endl; + + if (!r) + { + emit logError(i18n("The conduit %1 could not be executed.") + .arg(QString::fromLatin1(name()))); + delayDone(); + } +} + +/* slot */ void SyncAction::delayedDoneSlot() +{ + emit syncDone(this); +} + +bool SyncAction::delayDone() +{ + QTimer::singleShot(0,this,SLOT(delayedDoneSlot())); + return true; +} + +static struct +{ + SyncAction::SyncMode::Mode mode; + const char *name; +} maps[] = +{ + { SyncAction::SyncMode::eHotSync, "--hotsync" }, + { SyncAction::SyncMode::eFullSync, "--full" }, + { SyncAction::SyncMode::eCopyPCToHH, "--copyPCToHH" }, + { SyncAction::SyncMode::eCopyHHToPC, "--copyHHToPC" }, + { SyncAction::SyncMode::eBackup, "--backup" }, + { SyncAction::SyncMode::eRestore, "--restore" }, + { SyncAction::SyncMode::eFullSync, "--fullsync" }, + { SyncAction::SyncMode::eHotSync, (const char *)0 } +} +; + +SyncAction::SyncMode::SyncMode(const QStringList &args) : + fMode(eHotSync), + fTest(args.contains("--test")), + fLocal(args.contains("--local")) +{ + int i = 0; + while(maps[i].name) + { + if (args.contains(QString::fromLatin1(maps[i].name))) + { + fMode = maps[i].mode; + break; + } + i++; + } + + if (!maps[i].name) + { + WARNINGKPILOT << "No mode set by arguments (" + << args.join(",") << ") defaulting to HotSync." << endl; + } +} + +SyncAction::SyncMode::SyncMode(Mode m, bool test, bool local) : + fMode(m), + fTest(test), + fLocal(local) +{ + if ( ((int)m<(int)eHotSync) || ((int)m>(int)eRestore) ) + { + WARNINGKPILOT << "Mode value " << (int)m << " is illegal" + ", defaulting to HotSync." << endl; + fMode = eHotSync; + } +} + +QStringList SyncAction::SyncMode::list() const +{ + FUNCTIONSETUPL(3); + + QStringList l; + int i=0; + + while(maps[i].name) + { + if ( fMode == maps[i].mode ) + { + l.append(QString::fromLatin1(maps[i].name)); + break; + } + i++; + } + if ( !maps[i].name ) + { + WARNINGKPILOT << "Mode " << fMode << " does not have a name." << endl; + l.append(QString::fromLatin1(maps[0].name)); + } + + if (isTest()) l.append(CSL1("--test")); + if (isLocal()) l.append(CSL1("--local")); + return l; +} + +/* static */ QString SyncAction::SyncMode::name(SyncAction::SyncMode::Mode e) +{ + switch(e) + { + case eHotSync : return i18n("HotSync"); + case eFullSync : return i18n("Full Synchronization"); + case eCopyPCToHH : return i18n("Copy PC to Handheld"); + case eCopyHHToPC : return i18n("Copy Handheld to PC"); + case eBackup : return i18n("Backup"); + case eRestore : return i18n("Restore From Backup"); + } + return CSL1("<unknown>"); +} + +QString SyncAction::SyncMode::name() const +{ + QString s = name(fMode); + if (isTest()) + { + + s.append(CSL1(" [%1]").arg(i18n("Test Sync"))); + } + if (isLocal()) + { + s.append(CSL1(" [%1]").arg(i18n("Local Sync"))); + } + return s; +} + +bool SyncAction::SyncMode::setMode(int mode) +{ + // Resets test and local flags too + fTest = fLocal = false; + + if ( (mode>0) && (mode<=eRestore) ) + { + fMode = (SyncAction::SyncMode::Mode) mode; + return true; + } + else + { + WARNINGKPILOT << "Bad sync mode " << mode << " requested." << endl ; + fMode = eHotSync; + return false; + } +} + +bool SyncAction::SyncMode::setMode(SyncAction::SyncMode::Mode m) +{ + int i=0; + while ( maps[i].name ) + { + if ( maps[i].mode == m ) + { + fMode = m; + return true; + } + i++; + } + + WARNINGKPILOT << "Bad sync mode " << m << " requested." << endl ; + fMode = eHotSync; + return false; +} + +void SyncAction::startTickle(unsigned timeout) +{ + FUNCTIONSETUP; + + if (!deviceLink()) + { + WARNINGKPILOT << "Trying to tickle without a device." << endl; + } + else + { + connect(deviceLink(),SIGNAL(timeout()),this,SIGNAL(timeout())); + deviceLink()->startTickle(timeout); + } +} + +void SyncAction::stopTickle() +{ + FUNCTIONSETUP; + if (!deviceLink()) + { + WARNINGKPILOT << "Trying to tickle without a device." << endl; + } + else + { + disconnect(deviceLink(),SIGNAL(timeout()),this,SIGNAL(timeout())); + deviceLink()->stopTickle(); + } +} + + +int SyncAction::questionYesNo(const QString & text, + const QString & caption, + const QString & key, + unsigned timeout, + const QString & yes, + const QString &no ) +{ + FUNCTIONSETUP; + + bool checkboxReturn = false; + int r; + KMessageBox::ButtonCode result; + if (!key.isEmpty()) + { + if (!KMessageBox::shouldBeShownYesNo(key,result)) + { + return result; + } + } + + KDialogBase *dialog = + new KDialogBase(caption.isNull()? i18n("Question") : caption, + KDialogBase::Yes | KDialogBase::No, + KDialogBase::Yes, KDialogBase::No, + fParent, "questionYesNo", true, true, + yes.isEmpty() ? KStdGuiItem::yes() : yes, + no.isEmpty() ? KStdGuiItem::no() : no); + + if ( (timeout > 0) && ( deviceLink() ) ) + { + QObject::connect(deviceLink(), SIGNAL(timeout()), + dialog, SLOT(slotCancel())); + startTickle(timeout); + } + +#if KDE_IS_VERSION(3,3,0) + r = (KMessageBox::ButtonCode) KMessageBox::createKMessageBox(dialog, + QMessageBox::Question, + text, + QStringList(), + (key.isEmpty() ? QString::null : i18n("&Do not ask again")), + &checkboxReturn, + 0); + +#else + // The following code is taken from KDialogBase.cc, + // part of the KDE 2.2 libraries. Copyright 2001 + // by Waldo Bastian. + // + // + QVBox *topcontents = new QVBox(dialog); + + topcontents->setSpacing(KDialog::spacingHint() * 2); + topcontents->setMargin(KDialog::marginHint() * 2); + + QWidget *contents = new QWidget(topcontents); + QHBoxLayout *lay = new QHBoxLayout(contents); + + lay->setSpacing(KDialog::spacingHint() * 2); + + lay->addStretch(1); + QLabel *label1 = new QLabel( contents); + label1->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); + lay->add( label1 ); + QLabel *label2 = new QLabel( text, contents); + label2->setMinimumSize(label2->sizeHint()); + lay->add(label2); + lay->addStretch(1); + + QSize extraSize = QSize(50, 30); + + QCheckBox *checkbox = 0L; + if (!key.isEmpty()) + { + checkbox = new QCheckBox(i18n("Do not ask again"),topcontents); + extraSize = QSize(50,0); + } + + dialog->setMainWidget(topcontents); + dialog->enableButtonSeparator(false); + dialog->incInitialSize(extraSize); + + r = dialog->exec(); + if (checkbox) + { + checkboxReturn = checkbox->isChecked(); + } +#endif + + switch(r) + { + case KDialogBase::Yes : result=KMessageBox::Yes ; break; + case KDialogBase::No : result=KMessageBox::No; break; + case KDialogBase::Cancel : result=KMessageBox::Cancel; break; + default : break; + } + + stopTickle(); + + if (!key.isEmpty() && checkboxReturn) + { + KMessageBox::saveDontShowAgainYesNo(key,result); + } + + return result; +} + + +int SyncAction::questionYesNoCancel(const QString & text, + const QString & caption, + const QString & key, + unsigned timeout, + const QString &yes, + const QString &no) +{ + FUNCTIONSETUP; + + bool checkboxReturn = false; + int r; + KMessageBox::ButtonCode result; + + if (!key.isEmpty()) + { + if (!KMessageBox::shouldBeShownYesNo(key,result)) + { + if (result != KMessageBox::Cancel) + { + return result; + } + } + } + + KDialogBase *dialog = + new KDialogBase(caption.isNull()? i18n("Question") : caption, + KDialogBase::Yes | KDialogBase::No | KDialogBase::Cancel, + KDialogBase::Yes, KDialogBase::Cancel, + fParent, "questionYesNoCancel", true, true, + (yes.isEmpty() ? KStdGuiItem::yes() : yes), + (no.isEmpty() ? KStdGuiItem::no() : no), + KStdGuiItem::cancel()); + + if ( (timeout > 0) && (deviceLink()) ) + { + QObject::connect(deviceLink(), SIGNAL(timeout()), + dialog, SLOT(slotCancel())); + startTickle(timeout); + } + +#if KDE_IS_VERSION(3,3,0) + r = KMessageBox::createKMessageBox(dialog, + QMessageBox::Question, + text, + QStringList(), + (key.isEmpty() ? QString::null : i18n("&Do not ask again")), + &checkboxReturn, + 0); +#else + // The following code is taken from KDialogBase.cc, + // part of the KDE 2.2 libraries. Copyright 2001 + // by Waldo Bastian. + // + // + QVBox *topcontents = new QVBox(dialog); + + topcontents->setSpacing(KDialog::spacingHint() * 2); + topcontents->setMargin(KDialog::marginHint() * 2); + + QWidget *contents = new QWidget(topcontents); + QHBoxLayout *lay = new QHBoxLayout(contents); + + lay->setSpacing(KDialog::spacingHint() * 2); + + lay->addStretch(1); + QLabel *label1 = new QLabel( contents); + label1->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); + lay->add( label1 ); + QLabel *label2 = new QLabel( text, contents); + label2->setMinimumSize(label2->sizeHint()); + lay->add(label2); + lay->addStretch(1); + + QSize extraSize = QSize(50, 30); + + QCheckBox *checkbox = 0L; + if (!key.isEmpty()) + { + checkbox = new QCheckBox(i18n("Do not ask again"),topcontents); + extraSize = QSize(50,0); + } + + dialog->setMainWidget(topcontents); + dialog->enableButtonSeparator(false); + dialog->incInitialSize(extraSize); + + r = dialog->exec(); + if (checkbox) + { + checkboxReturn = checkbox->isChecked(); + } +#endif + + switch(r) + { + case KDialogBase::Yes : result=KMessageBox::Yes ; break; + case KDialogBase::No : result=KMessageBox::No; break; + case KDialogBase::Cancel : result=KMessageBox::Cancel; break; + default : break; + } + stopTickle(); + + if (!key.isEmpty() && checkboxReturn) + { + KMessageBox::saveDontShowAgainYesNo(key,result); + } + + return result; +} + diff --git a/kpilot/lib/syncAction.h b/kpilot/lib/syncAction.h new file mode 100644 index 000000000..a93bff99c --- /dev/null +++ b/kpilot/lib/syncAction.h @@ -0,0 +1,410 @@ +#ifndef _KPILOT_SYNCACTION_H +#define _KPILOT_SYNCACTION_H +/* KPilot +** +** Copyright (C) 1998-2001 by Dan Pilone +** Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> +** Copyright (C) 2006 Adriaan de Groot <groot@kde.org> +** +*/ + +/* +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program in a file called COPYING; if not, write to +** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +** MA 02110-1301, USA. +*/ + +/* +** Bug reports and questions can be sent to kde-pim@kde.org +*/ + +#include <time.h> + +#include <pi-dlp.h> + +#include <qobject.h> +#include <qstring.h> +#include <qstringlist.h> + +#include "kpilotlink.h" + +/** @file +* SyncAction +*/ + +class QTimer; +class KPilotUser; +class SyncAction; + +class KDE_EXPORT SyncAction : public QObject +{ +Q_OBJECT + +public: + SyncAction(KPilotLink *p, + const char *name=0L); + SyncAction(KPilotLink *p, + QWidget *visibleparent, + const char *name=0L); + ~SyncAction(); + + typedef enum { Error=-1 } Status; + + /** A syncaction has a status, which can be expressed as an + * integer. Subclasses are expected to define their own status + * values as needed. + */ + int status() const + { + return fActionStatus; + } + /** Return a human-readable representation of the status. */ + virtual QString statusString() const; + +protected: + /** + * This function starts the actual processing done + * by the conduit. It should return false if the + * processing cannot be initiated, f.ex. because + * some parameters were not set or a needed library + * is missing. This will be reported to the user. + * It should return true if processing is started + * normally. If processing starts normally, it is + * the _conduit's_ responsibility to eventually + * emit syncDone(); if processing does not start + * normally (ie. exec() returns false) then the + * environment will deal with syncDone(). + */ + virtual bool exec() = 0; + +public slots: + /** + * This just calls exec() and deals with the + * return code. + */ + void execConduit(); + +signals: + void syncDone(SyncAction *); + void logMessage(const QString &); + void logError(const QString &); + void logProgress(const QString &,int); + +protected slots: + /** This slot emits syncDone(), and does nothing else. This + * is safe, since the method returns immediately after the + * emit -- even if syncDone() causes the SyncAction to be deleted. + */ + void delayedDoneSlot(); + +protected: + /** + * It might not be safe to emit syncDone() from exec(). + * So instead, call delayDone() to wait for the main event + * loop to return if you manage to do all processing + * immediately. + * + * delayDone() returns true, so that return delayDone(); + * is a sensible final statement in exec(). + */ + bool delayDone(); + +public: + /** Public API for adding a sync log entry, see the implementation + * in KPilotLink::addSyncLogEntry(). + * @param e Message to add to the sync log + * @param log If @c true, also add the entry to the log in KPilot + * @note Having messages appear on the handheld but not in KPilot + * should be a @em very rare occurrence. + */ + void addSyncLogEntry(const QString &e,bool log=true) + { + if (deviceLink()) + { + deviceLink()->addSyncLogEntry(e,log); + } + } + /** Public API for adding a message to the log in KPilot. + * Adds @p msg to the synclog maintained on the PC. + */ + void addLogMessage( const QString &msg ) + { + emit logMessage( msg ); + } + /** Log an error message in KPilot (the PC side of things). */ + void addLogError( const QString &msg ) + { + emit logError( msg ); + } + /** Log progress in KPilot (the PC side of things). */ + void addLogProgress( const QString &msg, int prog ) + { + emit logProgress( msg, prog ); + } +protected: + /** Connection to the device. @todo make private. */ + KPilotLink *fHandle; + int fActionStatus; + + /** Returns a pointer to the connection to the device. */ + inline KPilotLink *deviceLink() const + { + return fHandle; + } + + /** Returns the file descriptor for the device link -- that is, + * the raw handle to the OS's connection to the device. Use with care. + * May return -1 if there is no device. + */ + int pilotSocket() const + { + return deviceLink() ? deviceLink()->pilotSocket() : -1 ; + } + + /** Tells the handheld device that someone is talking to it now. + * Useful (repeatedly) to inform the user of what is going on. + * May return < 0 on error (or if there is no device attached). + */ + int openConduit() + { + return deviceLink() ? deviceLink()->openConduit() : -1; + } +public: + /** + * This class encapsulates the different sync modes that + * can be used, and enforces a little discipline in changing + * the mode and messing around in general. It replaces a + * simple enum by not much more, but it makes things like + * local test backups less likely to happen. + * + * Note that this could all be packed into a bitfield (5 bits needed) + * but that makes for messy code in the end. + */ + class SyncMode + { + public: + /** Available modes for the sync. */ + enum Mode { + eHotSync=1, + eFullSync=2, + eCopyPCToHH=3, + eCopyHHToPC=4, + eBackup=5, + eRestore=6 + } ; + + /** Create a mode with the given Mode @p m and + * the mix-ins @p test and @p local, which + * determine whether the sync should actually change + * anything at all (test mode) and whether the HH is + * to be simulated by local databases. + */ + SyncMode(Mode m, bool test=false, bool local=false); + + /** Create a mode by parsing the string list. This + * is used mostly by the conduit proxies, which use + * a string list to pass aparameters to the shared + * library loader. + */ + SyncMode(const QStringList &l); + + /** Returns the kind of sync; this is just incomplete + * information, since a test hot sync is very different from + * a non-test one. */ + Mode mode() const + { + return fMode; + } + + /** Sets a mode from an integer @p mode, if possible. + * If the @p mode is illegal, return false and set the + * mode to Hot Sync. As a side effect, options test and local + * are reset to false. + */ + bool setMode(int); + + /** Sets a mode from a @p mode, if possible. This leaves + * the options unchanged, so as to reward properly-typed programming. + */ + bool setMode(Mode m); + + /** Sets options. Returns false if the combination of mode + * and the options is impossible. */ + bool setOptions(bool test, bool local) + { + fTest=test; + fLocal=local; + return true; + } + + /** Shorthand to test for a specific mode enum. This disregards + * the mixings local and test. + */ + bool operator ==(const Mode &m) const + { + return mode() == m; + } + /** Longhand comparison. Compares two modes for the same + * mode enum and mixins local and test. + */ + bool operator ==(const SyncMode &m) const + { + return ( mode() == m.mode() ) && + ( isTest() == m.isTest() ) && + ( isLocal() == m.isLocal() ); + } ; + + /** Accessor for the test part of the mode. Test syncs should + * never actually modify data anywhere. + */ + bool isTest() const + { + return fTest; + } + + /** Accessor for the local part of the mode. Local syncs use a + * local database instead of one on the device link. + */ + bool isLocal() const + { + return fLocal; + } + + bool isFullSync() const + { + return ( fMode==eFullSync ) || + ( fMode==eCopyPCToHH) || + ( fMode==eCopyHHToPC) ; + } ; + bool isFirstSync() const + { + return ( fMode==eCopyHHToPC ) || ( fMode==eCopyPCToHH ) ; + }; + + /** Classify every mode as either a sync (two-way) or copy (one-way) mode. */ + bool isSync() const + { + return ( fMode==eFullSync ) || + ( fMode == eHotSync ); + } ; + + /** Classify every mode as either a sync (two-way) or copy (one-way) mode. */ + bool isCopy() const + { + return ( fMode==eBackup ) || + ( fMode==eRestore ) || + ( fMode==eCopyPCToHH ) || + ( fMode==eCopyHHToPC ); + } ; + + /** + * Returns a standard name for each of the sync modes. + */ + static QString name(Mode); + + /** + * Returns a (human readable) name for this particular mode, + * including extra information about test and local mode. + */ + QString name() const; + + /** + * Returns a QStringList that, when passed to the constructor + * of SyncMode, will re-create it. Used to pass modes into + * shared library factories. + */ + QStringList list() const; + + private: + Mode fMode; + bool fTest; + bool fLocal; + }; + + + enum ConflictResolution + { + eUseGlobalSetting=-1, + eAskUser=0, + eDoNothing, + eHHOverrides, + ePCOverrides, + ePreviousSyncOverrides, + eDuplicate, + eDelete, + eCROffset=-1 + }; + + /** + * This MUST stay in sync with the combobox in + * kpilotConfigDialog_backup.ui. If it does not, you need to + * either change this enum or the combobox. + */ + enum BackupFrequency + { + eEveryHotSync=0, + eOnRequestOnly + }; + +protected: + /** + * Call startTickle() some time before showing a dialog to the + * user (we're assuming a local event loop here) so that while + * the dialog is up and the user is thinking, the pilot stays + * awake. Afterwards, call stopTickle(). + * + * The parameter to startTickle indicates the timeout, in + * seconds, before signal timeout is emitted. You can connect + * to that, again, to take down the user interface part if the + * user isn't reacting. + */ + void startTickle(unsigned count=0); + void stopTickle(); +signals: + void timeout(); + + + + +protected: + QWidget *fParent; + + /** + * Ask a yes-no question of the user. This has a timeout so that + * you don't wait forever for inattentive users. It's much like + * KMessageBox::questionYesNo(), but with this extra timeout-on- + * no-answer feature. Returns a KDialogBase::ButtonCode value - Yes,No or + * Cancel on timeout. If there is a key set and the user indicates not to ask again, + * the selected answer (Yes or No) is remembered for future reference. + * + * @p caption Message Box caption, uses "Question" if null. + * @p key Key for the "Don't ask again" code. + * @p timeout Timeout, in seconds. + */ + int questionYesNo(const QString &question , + const QString &caption = QString::null, + const QString &key = QString::null, + unsigned timeout = 20, + const QString &yes = QString::null, + const QString &no = QString::null ); + int questionYesNoCancel(const QString &question , + const QString &caption = QString::null, + const QString &key = QString::null, + unsigned timeout = 20, + const QString &yes = QString::null, + const QString &no = QString::null ) ; +}; + + +#endif |