]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1184 d1b88da0-ebda-0310...
authorfrancis <francis@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 11 Oct 2007 14:49:33 +0000 (14:49 +0000)
committerfrancis <francis@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 11 Oct 2007 14:49:33 +0000 (14:49 +0000)
36 files changed:
SOPE/sope-gdl1/GDLContentStore/COPYING [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/COPYRIGHT [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/ChangeLog [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSContext.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSContext.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolder.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolder.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GNUmakefile [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/README [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/Version [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/common.h [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/fhs.make [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/gcs_cat.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/gcs_ls.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m [new file with mode: 0644]
SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m [new file with mode: 0644]

diff --git a/SOPE/sope-gdl1/GDLContentStore/COPYING b/SOPE/sope-gdl1/GDLContentStore/COPYING
new file mode 100644 (file)
index 0000000..161a3d1
--- /dev/null
@@ -0,0 +1,482 @@
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 library GPL.  It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+                           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 Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it.  You can use it for
+your libraries, too.
+
+  When we speak of free software, we are referring to freedom, 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 or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the 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 a program 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.
+
+  Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library.  If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+\f
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software.  To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+  Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs.  This
+license, the GNU Library General Public License, applies to certain
+designated libraries.  This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+  The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it.  Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program.  However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+  Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries.  We
+concluded that weaker conditions might promote sharing better.
+
+  However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves.  This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them.  (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.)  The hope is that this
+will lead to faster development of free libraries.
+
+  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, while the latter only
+works together with the library.
+
+  Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+\f
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+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.
+\f
+  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.
+\f
+  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.
+\f
+  6. As an exception to the Sections above, you may also compile 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) 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.
+
+    c) 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.
+
+    d) 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 source code 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.
+\f
+  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 to
+this License.
+\f
+  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 Library 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.
+\f
+  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
+\f
+     Appendix: 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 Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 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
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free
+    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+    MA 02111-1307, 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/SOPE/sope-gdl1/GDLContentStore/COPYRIGHT b/SOPE/sope-gdl1/GDLContentStore/COPYRIGHT
new file mode 100644 (file)
index 0000000..1053b2a
--- /dev/null
@@ -0,0 +1,4 @@
+Copyright (C) 2004 SKYRIX Software AG
+
+
+Contact: info@skyrix.com
diff --git a/SOPE/sope-gdl1/GDLContentStore/ChangeLog b/SOPE/sope-gdl1/GDLContentStore/ChangeLog
new file mode 100644 (file)
index 0000000..bdd4c43
--- /dev/null
@@ -0,0 +1,329 @@
+2007-08-29  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * EOQualifier+GCS.m: rewrote comparison code, now uses UPPER instead of
+         the PostgreSQL specific ILIKE. Fixes OGo bug #1906 (v4.7.49)
+
+2007-07-20  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSFolderManager.m: added 'some' rollback after an error (v4.7.48)
+
+2007-07-20  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolderManager.m: fixed a bug in last check, DROP TABLE is allowed
+         to fail in the given context (bug #1883) (v4.7.47)
+
+2007-07-11  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSFolderManager.m: added some error checking, plenty of open ends
+         pending (eg folder creation not wrapped in a transaction) (v4.7.46)
+       
+2007-06-29  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolderManager.m: fixed folder creation to populate empty path
+         fields with NULLs (OGo bug #1883) (v4.7.45)
+
+2007-04-25  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolder.[hm]: added methods to delete ACL records (OGo bug #1866)
+         (v4.7.44)
+
+2007-04-22  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSChannelManager.m: improved error log (v4.7.43)
+
+2007-04-17  Helge Hess  <helge.hess@opengroupware.org>
+
+       * fixed a few GNUstep compilation warnings (v4.7.42)
+
+2007-03-21  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolder.[hm], GCSFolderManager.[hm]: added ability to create and
+         delete GCS folders programmatically (OGo bug #1850) (v4.7.41)
+
+2007-02-12  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSFolder.m: fixed a gnustep-base compilation warning (v4.7.40)
+
+2007-02-09  Helge Hess  <helge.hess@opengroupware.org>
+
+       * use -errorWithFormat:, fixed a few logging crashes (incomplete format
+         strings) (v4.5.39)
+
+2007-02-08  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolder.m: added a gnustep-base hack to properly format bool
+         numbers for SQL. Base returns YES or NO in -stringValue while
+         libFoundation/NGExt returns 0 or 1 (v4.5.39)
+
+2007-01-15  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * GCSFolder.[hm], GCSFolderManager.m: added support for content table
+         ACLs (v4.5.38)
+
+2006-08-31  Wolfgang Sourdeau  <WSourdeau@Inverse.CA>
+
+       * EOQualifier+GCS.m: added support for OR qualifiers and for case
+         insensitive-like qualifiers on PostgreSQL (v4.5.37)
+
+2006-07-04  Helge Hess  <helge.hess@opengroupware.org>
+
+       * use %p for pointer formats, fixed gcc 4.1 warnings (v4.5.36)
+
+2005-08-16  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GNUmakefile, GNUmakefile.preamble: added OSX framework compilation
+         (v4.5.35)
+
+2005-07-23  Sebastian Reitenbach  <reitenbach@rapideye.de>
+
+       * GNUmakefile.preamble: added OpenBSD linking flags (v4.5.34)
+
+2005-07-13  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSFolder.h: added -versionOfContentWithName: method to header file
+         (v4.5.33)
+
+       * GCSFolder.m: return a proper exception if the extractor was unable to
+         create a quickrow for a given content object (v4.5.32)
+
+       * GCSFolder.m: added -writeContent:toName:baseVersion: to support
+         consistent update operations (eg using etags), properly increase
+         content object version on update operations (v4.5.31)
+         
+       * GCSFolderManager.m, GCSFolder.m: changed not to use EOF
+         attribute-name 'beautification', eg 'c_name' will stay 'c_name' 
+         instead of being transformed into 'cName' (v4.5.30)
+
+2005-07-11  Helge Hess  <helge.hess@opengroupware.org>
+
+       * GCSFolderManager.m: added automatic discovery of folder types by
+         scanning for .ocs files (v4.5.29)
+
+2005-04-25  Helge Hess  <helge.hess@opengroupware.org>
+
+       * fixed gcc 4.0 warnings (v4.5.28)
+
+2005-03-21  Helge Hess  <helge.hess@skyrix.com>
+
+       * GNUmakefile: added FHS support (v4.5.27)
+
+2005-03-20  Helge Hess  <helge.hess@opengroupware.org>
+
+       * moved OGoContentStore as GDLContentStore into sope-gdl1, removed 
+         dependencies on NGiCal and removed some SOGo specific things
+         (v4.5.26)
+
+2005-03-07  Helge Hess  <helge.hess@opengroupware.org>
+
+       * appointment.ocs: added missing 'partstates' field (v0.9.25)
+
+2005-03-04  Helge Hess  <helge.hess@opengroupware.org>
+
+       * v0.9.24
+
+       * ocs_gensql.m: started tool to create SQL CREATE from ocs model file
+
+       * OCSFolderType.m: small change to the factory API, changed to use
+         NGResourceLocator
+
+2005-03-03  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSFolderManager.m: fixed a bug in subfolder listing (v0.9.23)
+
+2005-03-01  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v0.9.22
+
+       * appointment.ocs: added 'cycleenddate' and 'cycleinfo' to address
+         previous performance issues
+
+       * OCSiCalFieldExtractor.m: set 'cycleenddate' and 'cycleinfo' for
+         recurrent events. Reverted setting of 'enddate' to the previous
+         behaviour since 'cycleenddate' is dedicated to the task now
+       
+       * iCalRepeatableEntityObject+OCS.[hm]: new category used by the
+         OCSiCalFieldExtractor to extract cycleInfo in an appropriate format
+
+       * sql/generate-folderinfo-sql-for-users.sh,
+         sql/foldertablecreate-helge-privcal.psql,
+         sql/foldertablecreate-helge-privcal.sqlite,
+         sql/generate-folderinfo-sql-for-users-sqlite.sh: adjusted to new
+         schema
+
+2005-03-01  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSFolder.m: added support for storing content and quick info in
+         the same table (untested) (v0.9.21)
+
+2005-02-21  Helge Hess  <helge.hess@opengroupware.org>
+
+       * v0.9.20
+
+       * OCSFolderManager.m: removed quoting of SQL table and column names
+         (breaks with SQLite and isn't necessary for PG), fixed URL pooling
+         for SQLite
+
+       * NSURL+OCS.m: use tablename for last path component
+
+2005-02-12  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * OCSiCalFieldExtractor.m: uses new iCalEvent API to determine correct
+         'enddate' for recurrent events. This is an optimization which can
+         save quite some time for complex rules. (v0.9.19)
+
+2004-12-17  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v0.9.18
+
+       * OCSiCalFieldExtractor.m: extract participants' state
+
+       * sql/generate-folderinfo-sql-for-user.sh, sql/appointment-create.psql,
+         sql/foldertablecreate-helge-privcal.psql: updated with new schema.
+
+2004-12-15  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * OCSiCalFieldExtractor.m: partmails + cn's are concatenated by '\n'
+         now - this directly eliminates any ambiguities. Also, instead of
+         using 'email' for partmails and orgmail, the extractor uses the
+         'rfc822Email' value which strips away any preceeding 'mailto:'
+         prefix, compacting the representation and speeding up comparison.
+         Also, "iscycle", "isallday" and "isopaque" are now provided by
+         NGiCal and thus always extracted (v0.9.17)
+
+2004-12-13  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * sql/generate-folderinfo-sql-for-user.sh: fixed critical error in
+         Contacts folder_info, type was 'Appointment' but MUST be 'Contact'
+         (v0.9.16)
+
+2004-12-10  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * sql: updated all generation scripts to the latest version (v0.9.15)
+
+2004-12-09  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * v0.9.14
+
+       * appointment.ocs: added "ispublic", "isopaque", "status" and
+         "orgmail".
+
+       * OCSiCalFieldExtractor.m: updated to extract new fields (see above)
+
+       * sql: updated generate-folderinfo-sql-for-users.sh
+
+2004-10-19  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSFolder.m: added new method -fetchContentsOfAllFiles method which
+         fetches the contents of all files stored in the folder (required for
+         iCal generation, such bulk fetches should be avoided if possible!)
+         (v0.9.13)
+
+2004-10-15  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * OCSStringFormatter.[hm]: minor cleanup (v0.9.12)
+
+       * v0.9.11
+
+       * OCSStringFormatter.[hm]: new class to format strings according to
+         Database requirements (escaping etc.).
+
+       * OCSFolder.m: uses new OCSStringFormatter now.
+
+2004-09-25  Helge Hess  <helge.hess@opengroupware.org>
+
+       * fixed compilation on MacOSX (v0.9.10)
+
+2004-09-10  Helge Hess  <helge.hess@skyrix.com>
+
+       * v0.9.9
+
+       * fixed some gcc warnings
+
+       * GNUmakefile.preamble: added pathes to compile against an FHS SOPE
+
+       * OCSiCalFieldExtractor.m: fixed type of sequence iCalEvent field
+
+2004-09-01  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * GNUmakefile: install type models into $(GNUSTEP_USER_ROOT) (v0.9.8)
+
+2004-08-27  Helge Hess  <helge.hess@skyrix.com>
+
+       * v0.9.7
+       
+       * OCSChannelManager.m: use PostgreSQL as adaptor, not PostgreSQL72
+       
+       * OCSFolder.m: added support for doing folder sorting in SQL
+
+2004-08-26  Helge Hess  <helge.hess@skyrix.com>
+
+       * v0.9.6
+
+       * added OCSContactFieldExtractor
+       
+       * sql: added sample contact folder create scripts
+       
+       * OCSFolderType.m: read extractor class name from type model
+
+       * OCSFolderManager.m: added contact type model per default (v0.9.5)
+
+2004-08-25  Helge Hess  <helge.hess@skyrix.com>
+       
+       * GNUmakefile: automatically install OCSTypeModels (v0.9.4)
+       
+2004-08-15  Helge Hess  <helge.hess@skyrix.com>
+       
+       * OCSFolder.m: added content deletion (v0.9.3)
+       
+       * OCSFolder.m: added sanity check to store method (v0.9.2)
+       
+2004-08-14  Helge Hess  <helge.hess@skyrix.com>
+       
+       * v0.9.1
+       
+       * OCSiCalFieldExtractor.m: extract new quick fields: location, 
+         partmails, sequence (does not yet handle allday and cycle due to
+         NGiCal restrictions)
+       
+       * appointment.ocs, sql/foldertablecreate-helge-privcal.psql, 
+         sql/testapt-agenor-helge-privcal.psql, sql/appointment-create.psql:
+         added quick fields: isallday, iscycle, location, partmails, sequence
+       
+       * started ocs_recreatequick tool intended for recreating a quick table
+         based on the content table of a folder
+       
+2004-07-20  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSChannelManager.m: fixed a bug in the channel GC which resulted
+         in an exception during the GC NSTimer
+
+2004-07-16  Helge Hess  <helge.hess@skyrix.com>
+
+       * improved error handling in various files
+
+2004-07-02  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSChannelManager.m: added garbage collector for channel pools
+
+2004-06-30  Helge Hess  <helge.hess@opengroupware.org>
+
+       * OCSChannelManager.m: implemented pooling
+
+       * OCSFolder.m: added quick fetches
+
+       * GNUmakefile.preamble: fix link path
+
+       * GNUmakefile (libOGoContentStore_HEADER_FILES_INSTALL_DIR): install
+         headers in OGoContentStore
+
+       * GNUmakefile.preamble (ocs_ls_TOOL_LIBS): added static dependencies
+         for OSX
+
+2004-06-30  Marcus Mueller  <znek@mulle-kybernetik.com>
+
+       * ocs_cat.m, ocs_ls.m, ocs_mkdir.m: fixed for gnustep compile.
+
+2004-06-29  Helge Hess  <helge.hess@opengroupware.org>
+
+       * created ChangeLog
+
diff --git a/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.h
new file mode 100644 (file)
index 0000000..da28e55
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_EOAdaptorChannel_GCS_H__
+#define __GDLContentStore_EOAdaptorChannel_GCS_H__
+
+#include <GDLAccess/EOAdaptorChannel.h>
+
+@protocol GCSEOAdaptorChannel
+
+- (NSException *) createGCSFolderTableWithName: (NSString *) tableName;
+- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName;
+
+@end
+
+@interface EOAdaptorChannel(GCS)
+
+- (BOOL)tableExistsWithName:(NSString *)_tableName;
+
+@end
+
+#endif /* __GDLContentStore_EOAdaptorChannel_GCS_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m b/SOPE/sope-gdl1/GDLContentStore/EOAdaptorChannel+GCS.m
new file mode 100644 (file)
index 0000000..23e1561
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "EOAdaptorChannel+GCS.h"
+#include "common.h"
+
+@implementation EOAdaptorChannel(GCS)
+
+- (BOOL)tableExistsWithName:(NSString *)_tableName {
+  NSException *ex;
+  NSString    *sql;
+  BOOL        didOpen;
+  
+  didOpen = NO;
+  if (![self isOpen]) {
+    if (![self openChannel])
+      return NO;
+    didOpen = YES;
+  }
+  
+  sql = @"SELECT COUNT(*) FROM ";
+  sql = [sql stringByAppendingString:_tableName];
+  sql = [sql stringByAppendingString:@" WHERE 1 = 2"];
+  
+  ex = [[[self evaluateExpressionX:sql] retain] autorelease];
+  [self cancelFetch];
+  
+  if (didOpen) [self closeChannel];
+  return ex != nil ? NO : YES;
+}
+
+@end /* EOAdaptorChannel(GCS) */
diff --git a/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.h
new file mode 100644 (file)
index 0000000..27fae8d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_EOQualifier_GCS_H__
+#define __GDLContentStore_EOQualifier_GCS_H__
+
+#include <EOControl/EOQualifier.h>
+
+@class NSMutableString;
+
+@interface EOQualifier(GCS)
+
+- (void)_gcsAppendToString:(NSMutableString *)_ms;
+
+@end
+
+#endif /* __GDLContentStore_EOQualifier_GCS_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m b/SOPE/sope-gdl1/GDLContentStore/EOQualifier+GCS.m
new file mode 100644 (file)
index 0000000..8834335
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+  Copyright (C) 2004-2007 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "EOQualifier+GCS.h"
+#include "common.h"
+
+@implementation EOQualifier(GCS)
+
+- (void)_appendAndQualifier:(EOAndQualifier *)_q 
+  toString:(NSMutableString *)_ms
+{
+  // TODO: move to EOQualifier category
+  NSArray *qs;
+  unsigned i, count;
+  
+  qs = [_q qualifiers];
+  if ((count = [qs count]) == 0)
+    return;
+  
+  for (i = 0; i < count; i++) {
+    if (i != 0) [_ms appendString:@" AND "];
+    if (count > 1) [_ms appendString:@"("];
+    [[qs objectAtIndex:i] _gcsAppendToString:_ms];
+    if (count > 1) [_ms appendString:@")"];
+  }
+}
+- (void)_appendOrQualifier:(EOAndQualifier *)_q 
+  toString:(NSMutableString *)_ms
+{
+  // TODO: move to EOQualifier category
+  NSArray *qs;
+  unsigned i, count;
+  
+  qs = [_q qualifiers];
+  if ((count = [qs count]) == 0)
+    return;
+  
+  for (i = 0; i < count; i++) {
+    if (i != 0) [_ms appendString:@" OR "];
+    if (count > 1) [_ms appendString:@"("];
+    [[qs objectAtIndex:i] _gcsAppendToString:_ms];
+    if (count > 1) [_ms appendString:@")"];
+  }
+}
+- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q 
+  toString:(NSMutableString *)_ms
+{
+  id val;
+  NSString *qKey, *qOperator, *qValue, *qFormat;
+  BOOL isCI;
+
+  qKey = [_q key];
+
+  if ((val = [_q value])) {
+    SEL op = [_q selector];
+
+    if ([val isNotNull]) {
+      isCI = NO;
+
+      if (sel_eq(op, EOQualifierOperatorEqual))
+       qOperator = @"=";
+      else if (sel_eq(op, EOQualifierOperatorNotEqual))
+       qOperator = @"!=";
+      else if (sel_eq(op, EOQualifierOperatorLessThan))
+       qOperator = @"<";
+      else if (sel_eq(op, EOQualifierOperatorGreaterThan))
+       qOperator = @">";
+      else if (sel_eq(op, EOQualifierOperatorLessThanOrEqualTo))
+       qOperator = @"<=";
+      else if (sel_eq(op, EOQualifierOperatorGreaterThanOrEqualTo))
+       qOperator = @">=";
+      else if (sel_eq(op, EOQualifierOperatorLike))
+       qOperator = @"LIKE";
+      else if (sel_eq(op, EOQualifierOperatorCaseInsensitiveLike)) {
+       isCI = YES;
+       qOperator = @"LIKE";
+      }
+      else {
+       [self errorWithFormat:@"%s: unsupported operation for null: %@",
+             __PRETTY_FUNCTION__, NSStringFromSelector(op)];
+      }
+
+      if ([val isKindOfClass:[NSNumber class]])
+       qValue = [val stringValue];
+      else if ([val isKindOfClass:[NSString class]]) {
+       qValue = [NSString stringWithFormat: @"'%@'", val];
+      }
+      else {
+       [self errorWithFormat:@"%s: unsupported value class: %@",
+             __PRETTY_FUNCTION__, NSStringFromClass([val class])];
+      }
+    }
+    else {
+      if (sel_eq(op, EOQualifierOperatorEqual)) {
+       qOperator = @"IS";
+       qValue = @"NULL";
+      }
+      else if (sel_eq(op, EOQualifierOperatorEqual)) {
+       qOperator = @"IS NOT";
+       qValue = @"NULL";
+      }
+      else {
+       [self errorWithFormat:@"%s: invalid operation for null: %@",
+             __PRETTY_FUNCTION__, NSStringFromSelector(op)];
+      }
+    }
+  }
+  else {
+    qOperator = @"IS";
+    qValue = @"NULL";
+  }
+
+  if (isCI)
+    qFormat = @"UPPER(%@) %@ UPPER(%@)";
+  else
+    qFormat = @"%@ %@ %@";
+
+  [_ms appendFormat: qFormat, qKey, qOperator, qValue];
+}
+
+- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms {
+  if (_q == nil) return;
+  
+  if ([_q isKindOfClass:[EOAndQualifier class]])
+    [self _appendAndQualifier:(id)_q toString:_ms];
+  else if ([_q isKindOfClass:[EOOrQualifier class]])
+    [self _appendOrQualifier:(id)_q toString:_ms];
+  else if ([_q isKindOfClass:[EOKeyValueQualifier class]])
+    [self _appendKeyValueQualifier:(id)_q toString:_ms];
+  else
+    [self errorWithFormat:@"unknown qualifier: %@", _q];
+}
+
+- (void)_gcsAppendToString:(NSMutableString *)_ms {
+  [self _appendQualifier:self toString:_ms];
+}
+
+@end /* EOQualifier(GCS) */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.h
new file mode 100644 (file)
index 0000000..1e8bae6
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSChannelManager_H__
+#define __GDLContentStore_GCSChannelManager_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  GCSChannelManager
+
+  This object manages the connection pooling.
+*/
+
+@class NSURL, NSMutableDictionary, NSMutableArray, NSTimer;
+@class EOAdaptorChannel, EOAdaptor;
+
+@interface GCSChannelManager : NSObject
+{
+  NSMutableDictionary *urlToAdaptor;
+
+  NSMutableArray *availableChannels;
+  NSMutableArray *busyChannels;
+  NSTimer        *gcTimer;
+}
+
++ (id)defaultChannelManager;
+
+/* channels */
+
+- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url;
+- (void)releaseChannel:(EOAdaptorChannel *)_channel;
+
+/* checking for tables */
+
+- (BOOL)canConnect:(NSURL *)_url;
+
+@end
+
+#endif /* __GDLContentStore_GCSChannelManager_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m b/SOPE/sope-gdl1/GDLContentStore/GCSChannelManager.m
new file mode 100644 (file)
index 0000000..e198390
--- /dev/null
@@ -0,0 +1,516 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSChannelManager.h"
+#include "NSURL+GCS.h"
+#include "EOAdaptorChannel+GCS.h"
+#include <GDLAccess/EOAdaptor.h>
+#include <GDLAccess/EOAdaptorContext.h>
+#include <GDLAccess/EOAdaptorChannel.h>
+#include "common.h"
+
+/*
+  TODO:
+  - implemented pooling
+  - auto-close channels which are very old?! 
+    (eg missing release due to an exception)
+*/
+
+@interface GCSChannelHandle : NSObject
+{
+@public
+  NSURL            *url;
+  EOAdaptorChannel *channel;
+  NSDate           *creationTime;
+  NSDate           *lastReleaseTime;
+  NSDate           *lastAcquireTime;
+}
+
+- (EOAdaptorChannel *)channel;
+- (BOOL)canHandleURL:(NSURL *)_url;
+- (NSTimeInterval)age;
+
+@end
+
+@implementation GCSChannelManager
+
+static BOOL           debugOn                = NO;
+static BOOL           debugPools             = NO;
+static int            ChannelExpireAge       = 180;
+static NSTimeInterval ChannelCollectionTimer = 5 * 60;
+
++ (void)initialize {
+  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+  
+  debugOn    = [ud boolForKey:@"GCSChannelManagerDebugEnabled"];
+  debugPools = [ud boolForKey:@"GCSChannelManagerPoolDebugEnabled"];
+  
+  ChannelExpireAge = [[ud objectForKey:@"GCSChannelExpireAge"] intValue];
+  if (ChannelExpireAge < 1)
+    ChannelExpireAge = 180;
+  
+  ChannelCollectionTimer = 
+    [[ud objectForKey:@"GCSChannelCollectionTimer"] intValue];
+  if (ChannelCollectionTimer < 1)
+    ChannelCollectionTimer = 5*60;
+}
+
++ (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
+  // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL
+  return @"PostgreSQL";
+}
+
++ (id)defaultChannelManager {
+  static GCSChannelManager *cm = nil;
+  if (cm == nil)
+    cm = [[self alloc] init];
+  return cm;
+}
+
+- (id)init {
+  if ((self = [super init])) {
+    self->urlToAdaptor      = [[NSMutableDictionary alloc] initWithCapacity:4];
+    self->availableChannels = [[NSMutableArray alloc] initWithCapacity:16];
+    self->busyChannels      = [[NSMutableArray alloc] initWithCapacity:16];
+
+    self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
+                               ChannelCollectionTimer
+                             target:self selector:@selector(_garbageCollect:)
+                             userInfo:nil repeats:YES] retain];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  if (self->gcTimer) [self->gcTimer invalidate];
+  [self->gcTimer release];
+
+  [self->busyChannels      release];
+  [self->availableChannels release];
+  [self->urlToAdaptor      release];
+  [super dealloc];
+}
+
+/* DB key */
+
+- (NSString *)databaseKeyForURL:(NSURL *)_url {
+  /*
+    We need to build a proper key that omits passwords and URL path components
+    which are not required.
+  */
+  NSString *key;
+  
+  key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
+                 [_url host], [_url port],
+                 [_url user], [_url gcsDatabaseName]];
+  return key;
+}
+
+/* adaptors */
+
+- (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url {
+  NSMutableDictionary *md;
+  id tmp;
+  
+  md = [NSMutableDictionary dictionaryWithCapacity:4];
+
+  if ((tmp = [_url host]) != nil) 
+    [md setObject:tmp forKey:@"hostName"];
+  if ((tmp = [_url port]) != nil) 
+    [md setObject:tmp forKey:@"port"];
+  if ((tmp = [_url user]) != nil) 
+    [md setObject:tmp forKey:@"userName"];
+  if ((tmp = [_url password]) != nil) 
+    [md setObject:tmp forKey:@"password"];
+  
+  if ((tmp = [_url gcsDatabaseName]) != nil) 
+    [md setObject:tmp forKey:@"databaseName"];
+  
+  [self debugWithFormat:@"build connection dictionary for URL %@: %@", 
+       [_url absoluteString], md];
+  return md;
+}
+
+- (EOAdaptor *)adaptorForURL:(NSURL *)_url {
+  EOAdaptor *adaptor;
+  NSString  *key;
+  
+  if (_url == nil)
+    return nil;
+  if ((key = [self databaseKeyForURL:_url]) == nil)
+    return nil;
+  if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) {
+    [self debugWithFormat:@"using cached adaptor: %@", adaptor];
+    return adaptor; /* cached :-) */
+  }
+  
+  [self debugWithFormat:@"creating new adaptor for URL: %@", _url];
+  
+  if ([EOAdaptor respondsToSelector:@selector(adaptorForURL:)]) {
+    adaptor = [EOAdaptor adaptorForURL:_url];
+  }
+  else {
+    NSString     *adaptorName;
+    NSDictionary *condict;
+    
+    adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]];
+    if ([adaptorName length] == 0) {
+      [self errorWithFormat:@"cannot handle URL: %@", _url];
+      return nil;
+    }
+  
+    condict = [self connectionDictionaryForURL:_url];
+  
+    if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) {
+      [self errorWithFormat:@"did not find adaptor '%@' for URL: %@", 
+           adaptorName, _url];
+      return nil;
+    }
+  
+    [adaptor setConnectionDictionary:condict];
+  }
+  
+  [self->urlToAdaptor setObject:adaptor forKey:key];
+  return adaptor;
+}
+
+/* channels */
+
+- (GCSChannelHandle *)findBusyChannelHandleForChannel:(EOAdaptorChannel *)_ch {
+  NSEnumerator *e;
+  GCSChannelHandle *handle;
+  
+  e = [self->busyChannels objectEnumerator];
+  while ((handle = [e nextObject])) {
+    if ([handle channel] == _ch)
+      return handle;
+  }
+  return nil;
+}
+- (GCSChannelHandle *)findAvailChannelHandleForURL:(NSURL *)_url {
+  NSEnumerator *e;
+  GCSChannelHandle *handle;
+  
+  e = [self->availableChannels objectEnumerator];
+  while ((handle = [e nextObject])) {
+    if ([handle canHandleURL:_url])
+      return handle;
+    
+    if (debugPools) {
+      [self logWithFormat:@"DBPOOL: cannot use handle (%@ vs %@)",
+             [_url absoluteString], [handle->url absoluteString]];
+    }
+  }
+  return nil;
+}
+
+- (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url {
+  EOAdaptor        *adaptor;
+  EOAdaptorContext *adContext;
+  EOAdaptorChannel *adChannel;
+  
+  if ((adaptor = [self adaptorForURL:_url]) == nil)
+    return nil;
+  
+  if ((adContext = [adaptor createAdaptorContext]) == nil) {
+    [self errorWithFormat:@"could not create adaptor context!"];
+    return nil;
+  }
+  if ((adChannel = [adContext createAdaptorChannel]) == nil) {
+    [self errorWithFormat:@"could not create adaptor channel!"];
+    return nil;
+  }
+  return adChannel;
+}
+
+- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url {
+  // TODO: naive implementation, add pooling!
+  EOAdaptorChannel *channel;
+  GCSChannelHandle *handle;
+  NSCalendarDate   *now;
+
+  now = [NSCalendarDate date];
+  
+  /* look for cached handles */
+  
+  if ((handle = [self findAvailChannelHandleForURL:_url]) != nil) {
+    // TODO: check age?
+    [self->busyChannels      addObject:handle];
+    [self->availableChannels removeObject:handle];
+    ASSIGN(handle->lastAcquireTime, now);
+    
+    if (debugPools)
+      [self logWithFormat:@"DBPOOL: reused cached DB channel!"];
+    return [[handle channel] retain];
+  }
+
+  if (debugPools) {
+    [self logWithFormat:@"DBPOOL: create new DB channel for URL: %@",
+           [_url absoluteString]];
+  }
+  
+  /* create channel */
+  
+  if ((channel = [self _createChannelForURL:_url]) == nil)
+    return nil;
+  
+  if ([channel isOpen])
+    ;
+  else if (![channel openChannel]) {
+    [self errorWithFormat:@"could not open channel %@ for URL: %@",
+           channel, [_url absoluteString]];
+    return nil;
+  }
+  
+  /* create handle for channel */
+  
+  handle = [[GCSChannelHandle alloc] init];
+  handle->url             = [_url retain];
+  handle->channel         = [channel retain];
+  handle->creationTime    = [now retain];
+  handle->lastAcquireTime = [now retain];
+  
+  [self->busyChannels addObject:handle];
+  [handle release];
+  
+  return [channel retain];
+}
+- (void)releaseChannel:(EOAdaptorChannel *)_channel {
+  GCSChannelHandle *handle;
+  
+  if ((handle = [self findBusyChannelHandleForChannel:_channel]) != nil) {
+    NSCalendarDate *now;
+
+    now = [NSCalendarDate date];
+    
+    handle = [handle retain];
+    ASSIGN(handle->lastReleaseTime, now);
+    
+    [self->busyChannels removeObject:handle];
+    
+    if ([[handle channel] isOpen] && [handle age] < ChannelExpireAge) {
+      // TODO: consider age
+      [self->availableChannels addObject:handle];
+      if (debugPools) {
+       [self logWithFormat:
+               @"DBPOOL: keeping channel (age %ds, #%d): %@", 
+               (int)[handle age], [self->availableChannels count],
+               [handle->url absoluteString]];
+      }
+      [_channel release];
+      [handle release];
+      return;
+    }
+
+    if (debugPools) {
+      [self logWithFormat:
+             @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
+    }
+    
+    /* not reusing channel */
+    [handle release]; handle = nil;
+  }
+  
+  if ([_channel isOpen])
+    [_channel closeChannel];
+  
+  [_channel release];
+}
+
+/* checking for tables */
+
+- (BOOL)canConnect:(NSURL *)_url {
+  /* 
+     this can check for DB connect as well as for table URLs (whether a table
+     exists)
+  */
+  EOAdaptorChannel *channel;
+  NSString *table;
+  BOOL     result;
+  
+  if ((channel = [self acquireOpenChannelForURL:_url]) == nil) {
+    if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url];
+    return NO;
+  }
+  if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
+  result = YES; /* could open channel */
+  
+  /* check whether table exists */
+  
+  table = [_url gcsTableName];
+  if ([table length] > 0)
+    result = [channel tableExistsWithName:table];
+  
+  /* release channel */
+  
+  [self releaseChannel:channel]; channel = nil;
+  
+  return result;
+}
+
+/* collect old channels */
+
+- (void)_garbageCollect:(NSTimer *)_timer {
+  NSMutableArray *handlesToRemove;
+  unsigned i, count;
+  
+  if ((count = [self->availableChannels count]) == 0)
+    /* no available channels */
+    return;
+
+  /* collect channels to expire */
+  
+  handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4];
+  for (i = 0; i < count; i++) {
+    GCSChannelHandle *handle;
+    
+    handle = [self->availableChannels objectAtIndex:i];
+    if (![[handle channel] isOpen]) {
+      [handlesToRemove addObject:handle];
+      continue;
+    }
+    if ([handle age] > ChannelExpireAge) {
+      [handlesToRemove addObject:handle];
+      continue;
+    }
+  }
+  
+  /* remove channels */
+  count = [handlesToRemove count];
+  if (debugPools) 
+    [self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count];
+  for (i = 0; i < count; i++) {
+    GCSChannelHandle *handle;
+    
+    handle = [[handlesToRemove objectAtIndex:i] retain];
+    [self->availableChannels removeObject:handle];
+    if ([[handle channel] isOpen])
+      [[handle channel] closeChannel];
+    [handle release];
+  }
+  
+  [handlesToRemove release];
+}
+
+/* debugging */
+
+- (BOOL)isDebuggingEnabled {
+  return debugOn;
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+  
+  [ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]];
+  
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSChannelManager */
+
+@implementation GCSChannelHandle
+
+- (void)dealloc {
+  [self->channel         release];
+  [self->creationTime    release];
+  [self->lastReleaseTime release];
+  [self->lastAcquireTime release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (EOAdaptorChannel *)channel {
+  return self->channel;
+}
+
+- (BOOL)canHandleURL:(NSURL *)_url {
+  BOOL isSQLite;
+  
+  if (_url == nil) {
+    [self logWithFormat:@"MISMATCH: no url .."];
+    return NO;
+  }
+  if (_url == self->url)
+    return YES;
+
+  isSQLite = [[_url scheme] isEqualToString:@"sqlite"];
+  
+  if (!isSQLite && ![[self->url host] isEqual:[_url host]]) {
+    [self logWithFormat:@"MISMATCH: different host (%@ vs %@)",
+           [self->url host], [_url host]];
+    return NO;
+  }
+  if (![[self->url gcsDatabaseName] isEqualToString:[_url gcsDatabaseName]]) {
+    [self logWithFormat:@"MISMATCH: different db .."];
+    return NO;
+  }
+  if (!isSQLite) {
+    if (![[self->url user] isEqual:[_url user]]) {
+      [self logWithFormat:@"MISMATCH: different user .."];
+      return NO;
+    }
+    if ([[self->url port] intValue] != [[_url port] intValue]) {
+      [self logWithFormat:@"MISMATCH: different port (%@ vs %@) ..",
+         [self->url port], [_url port]];
+      return NO;
+    }
+  }
+  return YES;
+}
+
+- (NSTimeInterval)age {
+  return [[NSCalendarDate calendarDate] 
+                         timeIntervalSinceDate:self->creationTime];
+}
+
+/* NSCopying */
+
+- (id)copyWithZone:(NSZone *)_zone {
+  return [self retain];
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+  
+  [ms appendFormat:@" channel=0x%p", self->channel];
+  if (self->creationTime) [ms appendFormat:@" created=%@", self->creationTime];
+  if (self->lastReleaseTime) 
+    [ms appendFormat:@" last-released=%@", self->lastReleaseTime];
+  if (self->lastAcquireTime) 
+    [ms appendFormat:@" last-acquired=%@", self->lastAcquireTime];
+  
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSChannelHandle */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSContext.h b/SOPE/sope-gdl1/GDLContentStore/GCSContext.h
new file mode 100644 (file)
index 0000000..f386e65
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSContext_H__
+#define __GDLContentStore_GCSContext_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  GCSContext
+  
+  Context passed to all operations.
+*/
+
+@interface GCSContext : NSObject
+{
+}
+
+@end
+
+#endif /* __GDLContentStore_GCSContext_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSContext.m b/SOPE/sope-gdl1/GDLContentStore/GCSContext.m
new file mode 100644 (file)
index 0000000..17dd485
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSContext.h"
+#include "common.h"
+
+@implementation GCSContext
+@end /* GCSContext */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.h
new file mode 100644 (file)
index 0000000..1b1e64a
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSFieldExtractor_H__
+#define __GDLContentStore_GCSFieldExtractor_H__
+
+#import <Foundation/NSObject.h>
+
+@class NSString, NSMutableDictionary;
+
+@interface GCSFieldExtractor : NSObject
+{
+}
+
+- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content;
+
+@end
+
+#endif /* __GDLContentStore_GCSFieldExtractor_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m b/SOPE/sope-gdl1/GDLContentStore/GCSFieldExtractor.m
new file mode 100644 (file)
index 0000000..6ec8af6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSFieldExtractor.h"
+#include "common.h"
+
+@implementation GCSFieldExtractor
+
+- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content {
+  return nil;
+}
+
+@end /* GCSFieldExtractor */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.h
new file mode 100644 (file)
index 0000000..4f87f09
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSFieldInfo_H__
+#define __GDLContentStore_GCSFieldInfo_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  GCSFieldInfo
+  
+  The field info inside an .ocs schema file.
+  
+  The field info objects are stored in an GCSFolderType.
+*/
+
+@class NSString, NSArray;
+
+@interface GCSFieldInfo : NSObject
+{
+  NSString *columnName;
+  NSString *sqlType;
+  BOOL     allowsNull;
+  BOOL     isPrimaryKey;
+}
+
++ (NSArray *)fieldsForPropertyList:(NSArray *)_plist;
+- (id)initWithPropertyList:(id)_plist;
+
+/* accessors */
+
+- (NSString *)columnName;
+- (NSString *)sqlType;
+- (BOOL)doesAllowNull;
+- (BOOL)isPrimaryKey;
+
+/* generating SQL */
+
+- (NSString *)sqlCreateSection;
+
+@end
+
+#endif /* __GDLContentStore_GCSFieldInfo_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m b/SOPE/sope-gdl1/GDLContentStore/GCSFieldInfo.m
new file mode 100644 (file)
index 0000000..96a8578
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSFieldInfo.h"
+#include "common.h"
+
+@implementation GCSFieldInfo
+
++ (NSArray *)fieldsForPropertyList:(NSArray *)_plist {
+  NSMutableArray *fields;
+  unsigned i, count;
+  
+  if (_plist == nil)
+    return nil;
+  
+  count = [_plist count];
+  fields = [NSMutableArray arrayWithCapacity:count];
+  for (i = 0; i < count; i++) {
+    GCSFieldInfo *field;
+
+    field = [[GCSFieldInfo alloc] initWithPropertyList:
+                                   [_plist objectAtIndex:i]];
+    if (field != nil) [fields addObject:field];
+    [field release];
+  }
+  return fields;
+}
+
+- (id)initWithPropertyList:(id)_plist {
+  if ((self = [super init])) {
+    NSDictionary *plist = _plist;
+    
+    self->columnName = [[plist objectForKey:@"columnName"] copy];
+    self->sqlType    = [[plist objectForKey:@"sqlType"]    copy];
+    
+    self->allowsNull   = [[plist objectForKey:@"allowsNull"]   boolValue];
+    self->isPrimaryKey = [[plist objectForKey:@"isPrimaryKey"] boolValue];
+    
+    if (![self->columnName isNotNull] || ![self->sqlType isNotNull]) {
+      [self release];
+      return nil;
+    }
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [self->columnName release];
+  [self->sqlType    release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (NSString *)columnName {
+  return self->columnName;
+}
+- (NSString *)sqlType {
+  return self->sqlType;
+}
+
+- (BOOL)doesAllowNull {
+  return self->allowsNull;
+}
+- (BOOL)isPrimaryKey {
+  return self->isPrimaryKey;
+}
+
+/* generating SQL */
+
+- (NSString *)sqlCreateSection {
+  NSMutableString *ms;
+  
+  ms = [NSMutableString stringWithCapacity:32];
+  [ms appendString:[self columnName]];
+  [ms appendString:@" "];
+  [ms appendString:[self sqlType]];
+  
+  [ms appendString:@" "];
+  if (![self doesAllowNull]) [ms appendString:@"NOT "];
+  [ms appendString:@"NULL"];
+  
+  if ([self isPrimaryKey]) [ms appendString:@" PRIMARY KEY"];
+  return ms;
+}
+
+/* description */
+
+- (void)appendAttributesToDescription:(NSMutableString *)ms {
+  id tmp;
+  
+  if ((tmp = [self columnName]) != nil) [ms appendFormat:@" column=%@", tmp];
+  if ((tmp = [self sqlType])    != nil) [ms appendFormat:@" sql=%@",    tmp];
+  
+  if ([self doesAllowNull]) [ms appendString:@" allows-null"];
+  if ([self isPrimaryKey])  [ms appendString:@" pkey"];
+}
+
+- (NSString *)description {
+  NSMutableString *ms;
+  
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+  [self appendAttributesToDescription:ms];
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSFieldInfo */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolder.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.h
new file mode 100644 (file)
index 0000000..7209517
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+  Copyright (C) 2004-2007 SKYRIX Software AG
+  Copyright (C) 2007      Helge Hess
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSFolder_H__
+#define __GDLContentStore_GCSFolder_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  GCSFolder
+  
+  TODO: document
+  
+  Fixed Quick-Table SQL fields:
+  - "c_name" (name of the file in the folder)
+  
+  Fixed BLOB-Table SQL fields:
+  - "c_name"    (name of the file in the folder)
+  - "c_content" (content of the file in the folder)
+  - "c_version" (update revision of the file in the folder)
+*/
+
+@class NSString, NSURL, NSNumber, NSArray, NSException, NSMutableString;
+@class NSDictionary;
+@class EOQualifier, EOFetchSpecification;
+@class EOAdaptorChannel;
+@class GCSFolderManager, GCSFolderType, GCSChannelManager;
+
+@interface GCSFolder : NSObject
+{
+  GCSFolderManager *folderManager;
+  GCSFolderType    *folderInfo;
+  
+  NSNumber *folderId;
+  NSString *folderName;
+  NSString *path;
+  NSURL    *location;
+  NSURL    *quickLocation;
+  NSURL    *aclLocation;
+  NSString *folderTypeName;
+
+  struct {
+    int requiresFolderSelect:1;
+    int sameTableForQuick:1;
+    int reserved:30;
+  } ofFlags;
+}
+
+- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
+  folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
+  location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
+  aclLocation: (NSURL *)_aloc
+  folderManager:(GCSFolderManager *)_fm;
+
+/* accessors */
+
+- (NSNumber *)folderId;
+- (NSString *)folderName;
+- (NSString *)path;
+- (NSURL    *)location;
+- (NSURL    *)quickLocation;
+- (NSURL    *)aclLocation;
+- (NSString *)folderTypeName;
+
+- (GCSFolderManager *)folderManager;
+- (GCSChannelManager *)channelManager;
+
+- (NSString *)storeTableName;
+- (NSString *)quickTableName;
+- (NSString *)aclTableName;
+- (BOOL)isQuickInfoStoredInContentTable;
+
+/* connection */
+
+- (EOAdaptorChannel *)acquireStoreChannel;
+- (EOAdaptorChannel *)acquireQuickChannel;
+- (EOAdaptorChannel *)acquireAclChannel;
+- (void)releaseChannel:(EOAdaptorChannel *)_channel;
+
+- (BOOL)canConnectStore;
+- (BOOL)canConnectQuick;
+
+/* operations */
+
+- (NSArray *)subFolderNames;
+- (NSArray *)allSubFolderNames;
+
+- (NSNumber *)versionOfContentWithName:(NSString *)_name;
+
+- (NSString *)fetchContentWithName:(NSString *)_name;
+- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name
+  baseVersion:(unsigned int)_baseVersion;
+- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name;
+- (NSException *)deleteContentWithName:(NSString *)_name;
+
+- (NSException *)deleteFolder;
+
+- (NSDictionary *)fetchContentsOfAllFiles;
+
+- (NSArray *)fetchFields:(NSArray *)_flds 
+  fetchSpecification:(EOFetchSpecification *)_fs;
+- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q;
+- (NSArray *)fetchAclMatchingQualifier:(EOQualifier *)_q;
+- (void)deleteAclMatchingQualifier:(EOQualifier *)_q;
+- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs;
+
+@end
+
+#endif /* __GDLContentStore_GCSFolder_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolder.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolder.m
new file mode 100644 (file)
index 0000000..be7f22c
--- /dev/null
@@ -0,0 +1,1101 @@
+/*
+  Copyright (C) 2004-2007 SKYRIX Software AG
+  Copyright (C) 2007      Helge Hess
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSFolder.h"
+#include "GCSFolderManager.h"
+#include "GCSFolderType.h"
+#include "GCSChannelManager.h"
+#include "GCSFieldExtractor.h"
+#include "NSURL+GCS.h"
+#include "EOAdaptorChannel+GCS.h"
+#include "EOQualifier+GCS.h"
+#include "GCSStringFormatter.h"
+#include "common.h"
+
+#include <GDLAccess/EOEntity.h>
+#include <GDLAccess/EOAttribute.h>
+#include <GDLAccess/EOSQLQualifier.h>
+#include <GDLAccess/EOAdaptorContext.h>
+
+#define CHECKERROR() \
+ if (error) { \
+   [[storeChannel adaptorContext] rollbackTransaction]; \
+   [[quickChannel adaptorContext] rollbackTransaction]; \
+   [self logWithFormat:@"ERROR(%s): cannot %s content : %@", \
+        __PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", error]; \
+   return error; \
+ } \
+
+@implementation GCSFolder
+
+static BOOL debugOn    = NO;
+static BOOL doLogStore = NO;
+
+static Class NSStringClass       = Nil;
+static Class NSNumberClass       = Nil;
+static Class NSCalendarDateClass = Nil;
+
+static GCSStringFormatter *stringFormatter = nil;
+
++ (void)initialize {
+  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+  
+  debugOn         = [ud boolForKey:@"GCSFolderDebugEnabled"];
+  doLogStore      = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
+
+  NSStringClass       = [NSString class];
+  NSNumberClass       = [NSNumber class];
+  NSCalendarDateClass = [NSCalendarDate class];
+  
+  stringFormatter = [GCSStringFormatter sharedFormatter];
+}
+
+- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
+  folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
+  location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
+  aclLocation:(NSURL *)_aloc
+  folderManager:(GCSFolderManager *)_fm
+{
+  if (![_loc isNotNull]) {
+    [self errorWithFormat:@"missing quicktable parameter!"];
+    [self release];
+    return nil;
+  }
+  
+  if ((self = [super init])) {
+    self->folderManager  = [_fm    retain];
+    self->folderInfo     = [_ftype retain];
+    
+    self->folderId       = [_folderId copy];
+    self->folderName     = [[_path lastPathComponent] copy];
+    self->path           = [_path   copy];
+    self->location       = [_loc    retain];
+    self->quickLocation  = _qloc ? [_qloc   retain] : [_loc retain];
+    self->aclLocation    = [_aloc   retain];
+    self->folderTypeName = [_ftname copy];
+
+    self->ofFlags.requiresFolderSelect = 0;
+    self->ofFlags.sameTableForQuick = 
+      [self->location isEqualTo:self->quickLocation] ? 1 : 0;
+  }
+  return self;
+}
+- (id)init {
+  return [self initWithPath:nil primaryKey:nil
+              folderTypeName:nil folderType:nil 
+              location:nil quickLocation:nil
+               aclLocation:nil
+              folderManager:nil];
+}
+- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
+  folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
+  location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
+  folderManager:(GCSFolderManager *)_fm
+{
+  return [self initWithPath:_path primaryKey:_folderId folderTypeName:_ftname
+              folderType:_ftype location:_loc quickLocation:_qloc
+              aclLocation:nil
+              folderManager:_fm];
+}
+
+- (void)dealloc {
+  [self->folderManager  release];
+  [self->folderInfo     release];
+  [self->folderId       release];
+  [self->folderName     release];
+  [self->path           release];
+  [self->location       release];
+  [self->quickLocation  release];
+  [self->aclLocation    release];
+  [self->folderTypeName release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (NSNumber *)folderId {
+  return self->folderId;
+}
+
+- (NSString *)folderName {
+  return self->folderName;
+}
+- (NSString *)path {
+  return self->path;
+}
+
+- (NSURL *)location {
+  return self->location;
+}
+- (NSURL *)quickLocation {
+  return self->quickLocation;
+}
+- (NSURL *)aclLocation {
+  return self->aclLocation;
+}
+
+- (NSString *)folderTypeName {
+  return self->folderTypeName;
+}
+
+- (GCSFolderManager *)folderManager {
+  return self->folderManager;
+}
+- (GCSChannelManager *)channelManager {
+  return [[self folderManager] channelManager];
+}
+
+- (NSString *)storeTableName {
+  return [[self location] gcsTableName];
+}
+- (NSString *)quickTableName {
+  return [[self quickLocation] gcsTableName];
+}
+- (NSString *)aclTableName {
+  return [[self aclLocation] gcsTableName];
+}
+
+- (BOOL)isQuickInfoStoredInContentTable {
+  return self->ofFlags.sameTableForQuick ? YES : NO;
+}
+
+/* channels */
+
+- (EOAdaptorChannel *)acquireStoreChannel {
+  return [[self channelManager] acquireOpenChannelForURL:[self location]];
+}
+- (EOAdaptorChannel *)acquireQuickChannel {
+  return [[self channelManager] acquireOpenChannelForURL:[self quickLocation]];
+}
+- (EOAdaptorChannel *)acquireAclChannel {
+  return [[self channelManager] acquireOpenChannelForURL:[self aclLocation]];
+}
+
+- (void)releaseChannel:(EOAdaptorChannel *)_channel {
+  [[self channelManager] releaseChannel:_channel];
+  if (debugOn) [self debugWithFormat:@"released channel: %@", _channel];
+}
+
+- (BOOL)canConnectStore {
+  return [[self channelManager] canConnect:[self location]];
+}
+- (BOOL)canConnectQuick {
+  return [[self channelManager] canConnect:[self quickLocation]];
+}
+- (BOOL)canConnectAcl {
+  return [[self channelManager] canConnect:[self quickLocation]];
+}
+
+/* errors */
+
+- (NSException *)errorVersionMismatchBetweenStoredVersion:(unsigned int)_store
+  andExpectedVersion:(unsigned int)_base
+{
+  NSDictionary *ui;
+
+  ui = [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSNumber numberWithUnsignedInt:_base],  
+                      @"GCSExpectedVersion",
+                      [NSNumber numberWithUnsignedInt:_store],
+                      @"GCSStoredVersion",
+                      self, @"GCSFolder",
+                      nil];
+  
+  return [NSException exceptionWithName:@"GCSVersionMismatch"
+                     reason:@"Transaction conflict during a GCS modification."
+                     userInfo:ui];
+}
+
+- (NSException *)errorExtractorReturnedNoQuickRow:(id)_extractor
+  forContent:(NSString *)_content
+{
+  NSDictionary *ui;
+
+  ui = [NSDictionary dictionaryWithObjectsAndKeys:
+                      self,       @"GCSFolder",
+                      _extractor, @"GCSExtractor",
+                      _content,   @"GCSContent",
+                      nil];
+  return [NSException exceptionWithName:@"GCSExtractFailed"
+                     reason:@"Quickfield extractor did not return a result!"
+                     userInfo:ui];
+}
+
+/* operations */
+
+- (NSArray *)subFolderNames {
+  return [[self folderManager] listSubFoldersAtPath:[self path]
+                              recursive:NO];
+}
+- (NSArray *)allSubFolderNames {
+  return [[self folderManager] listSubFoldersAtPath:[self path]
+                              recursive:YES];
+}
+
+- (id)_fetchValueOfColumn:(NSString *)_col inContentWithName:(NSString *)_name{
+  EOAdaptorChannel *channel;
+  NSException  *error;
+  NSDictionary *row;
+  NSArray      *attrs;
+  NSString     *result;
+  NSString     *sql;
+  
+  if ((channel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@"could not open storage channel!"];
+    return nil;
+  }
+  
+  /* generate SQL */
+  
+  sql = @"SELECT ";
+  sql = [sql stringByAppendingString:_col];
+  sql = [sql stringByAppendingString:@" FROM "];
+  sql = [sql stringByAppendingString:[self storeTableName]];
+  sql = [sql stringByAppendingString:@" WHERE c_name = '"];
+  sql = [sql stringByAppendingString:_name];
+  sql = [sql stringByAppendingString:@"'"];
+  
+  /* run SQL */
+  
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    [self errorWithFormat:@"%s: cannot execute SQL '%@': %@", 
+           __PRETTY_FUNCTION__, sql, error];
+    [self releaseChannel:channel];
+    return nil;
+  }
+  
+  /* fetch results */
+  
+  result = nil;
+  attrs  = [channel describeResults:NO /* do not beautify names */];
+  if ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
+    result = [[[row objectForKey:_col] copy] autorelease];
+    if (![result isNotNull]) result = nil;
+    [channel cancelFetch];
+  }
+  
+  /* release and return result */
+  
+  [self releaseChannel:channel];
+  return result;
+}
+
+- (NSNumber *)versionOfContentWithName:(NSString *)_name {
+  return [self _fetchValueOfColumn:@"c_version" inContentWithName:_name];
+}
+
+- (NSString *)fetchContentWithName:(NSString *)_name {
+  return [self _fetchValueOfColumn:@"c_content" inContentWithName:_name];
+}
+
+- (NSDictionary *)fetchContentsOfAllFiles {
+  /*
+    Note: try to avoid the use of this method! The key of the dictionary
+          will be filename, the value the content.
+  */
+  NSMutableDictionary *result;
+  EOAdaptorChannel *channel;
+  NSException  *error;
+  NSDictionary *row;
+  NSArray      *attrs;
+  NSString     *sql;
+  
+  if ((channel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@"%s: could not open storage channel!",
+            __PRETTY_FUNCTION__];
+    return nil;
+  }
+  
+  /* generate SQL */
+  
+  sql = @"SELECT c_name, c_content FROM ";
+  sql = [sql stringByAppendingString:[self storeTableName]];
+  
+  /* run SQL */
+  
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    [self logWithFormat:@"ERROR(%s): cannot execute SQL '%@': %@", 
+           __PRETTY_FUNCTION__, sql, error];
+    [self releaseChannel:channel];
+    return nil;
+  }
+  
+  /* fetch results */
+  
+  result = [NSMutableDictionary dictionaryWithCapacity:128];
+  attrs  = [channel describeResults:NO /* do not beautify names */];
+  while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
+    NSString *cName, *cContent;
+    
+    cName    = [row objectForKey:@"c_name"];
+    cContent = [row objectForKey:@"c_content"];
+    
+    if (![cName isNotNull]) {
+      [self errorWithFormat:@"missing c_name in row: %@", row];
+      continue;
+    }
+    if (![cContent isNotNull]) {
+      [self errorWithFormat:@"missing c_content in row: %@", row];
+      continue;
+    }
+    
+    [result setObject:cContent forKey:cName];
+  }
+  
+  /* release and return result */
+  
+  [self releaseChannel:channel];
+  return result;
+}
+
+/* writing content */
+
+- (NSString *)_formatRowValue:(id)_value {
+
+  if (![_value isNotNull])
+    return @"NULL";
+
+  if ([_value isKindOfClass:NSStringClass])
+    return [stringFormatter stringByFormattingString:_value];
+
+  if ([_value isKindOfClass:NSNumberClass]) {
+#if GNUSTEP_BASE_LIBRARY
+    _value = [_value stringValue];
+    return ([(NSString *)_value hasPrefix:@"Y"] || 
+           [(NSString *)_value hasPrefix:@"N"])
+      ? (id)([_value boolValue] ? @"1" : @"0")
+      : _value;
+#endif
+    return [_value stringValue];
+  }
+  
+  if ([_value isKindOfClass:NSCalendarDateClass]) {
+    /* be smart ... convert to timestamp. Note: we loose precision. */
+    char buf[256];
+    snprintf(buf, sizeof(buf), "%i", (int)[_value timeIntervalSince1970]);
+    return [NSString stringWithCString:buf];
+  }
+  
+  [self errorWithFormat:@"cannot handle value class: %@", [_value class]];
+  return nil;
+}
+
+- (NSString *)_generateInsertStatementForRow:(NSDictionary *)_row
+  tableName:(NSString *)_table
+{
+  // TODO: move to NSDictionary category?
+  NSMutableString *sql;
+  NSArray  *keys;
+  unsigned i, count;
+
+  if (_row == nil || _table == nil)
+    return nil;
+
+  keys = [_row allKeys];
+
+  sql = [NSMutableString stringWithCapacity:512];
+  [sql appendString:@"INSERT INTO "];
+  [sql appendString:_table];
+  [sql appendString:@" ("];
+
+  for (i = 0, count = [keys count]; i < count; i++) {
+    if (i != 0) [sql appendString:@", "];
+    [sql appendString:[keys objectAtIndex:i]];
+  }
+
+  [sql appendString:@") VALUES ("];
+
+  for (i = 0, count = [keys count]; i < count; i++) {
+    id value;
+
+    if (i != 0) [sql appendString:@", "];
+    value = [_row objectForKey:[keys objectAtIndex:i]];
+    value = [self _formatRowValue:value];
+    [sql appendString:value];
+  }
+
+  [sql appendString:@")"];
+  return sql;
+}
+
+- (NSString *)_generateUpdateStatementForRow:(NSDictionary *)_row
+  tableName:(NSString *)_table
+  whereColumn:(NSString *)_colname isEqualTo:(id)_value
+  andColumn:(NSString *)_colname2  isEqualTo:(id)_value2
+{
+  // TODO: move to NSDictionary category?
+  NSMutableString *sql;
+  NSArray  *keys;
+  unsigned i, count;
+
+  if (_row == nil || _table == nil)
+    return nil;
+
+  keys = [_row allKeys];
+
+  sql = [NSMutableString stringWithCapacity:512];
+  [sql appendString:@"UPDATE "];
+  [sql appendString:_table];
+
+  [sql appendString:@" SET "];
+  for (i = 0, count = [keys count]; i < count; i++) {
+    id value;
+
+    value = [_row objectForKey:[keys objectAtIndex:i]];
+    value = [self _formatRowValue:value];
+
+    if (i != 0) [sql appendString:@", "];
+    [sql appendString:[keys objectAtIndex:i]];
+    [sql appendString:@" = "];
+    [sql appendString:value];
+  }
+
+  [sql appendString:@" WHERE "];
+  [sql appendString:_colname];
+  [sql appendString:@" = "];
+  [sql appendString:[self _formatRowValue:_value]];
+
+  if (_colname2 != nil) {
+    [sql appendString:@" AND "];
+    [sql appendString:_colname2];
+    [sql appendString:@" = "];
+    [sql appendString:[self _formatRowValue:_value2]];
+  }
+
+  return sql;
+}
+
+
+- (EOEntity *) _entityWithName: (NSString *) _name
+{
+  EOAttribute *attribute;
+  EOEntity *entity;
+  
+  entity = AUTORELEASE([[EOEntity alloc] init]);
+  [entity setName: _name];
+  [entity setExternalName: _name];
+
+  attribute = AUTORELEASE([[EOAttribute alloc] init]);
+  [attribute setName: @"c_name"];
+  [attribute setColumnName: @"c_name"];
+  [entity addAttribute: attribute];
+
+  return entity;
+}
+
+- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname
+                                     isEqualTo:(id)_value
+                                     andColumn:(NSString *)_colname2 
+                                     isEqualTo:(id)_value2
+                                        entity: (EOEntity *)_entity
+{
+  EOSQLQualifier *qualifier;
+
+  if (_colname2 == nil)
+    {
+      qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity
+                                         qualifierFormat: @"%A = %@", _colname, [self _formatRowValue:_value]];
+    }
+  else
+    {
+      qualifier = [[EOSQLQualifier alloc] initWithEntity: _entity
+                                         qualifierFormat: @"%A = %@ AND %A = %@", _colname, [self _formatRowValue:_value],
+                                         _colname2, [self _formatRowValue:_value2]];
+    }
+
+  return AUTORELEASE(qualifier);
+}
+
+- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name
+  baseVersion:(unsigned int)_baseVersion
+{
+  EOAdaptorChannel    *storeChannel, *quickChannel;
+  NSMutableDictionary *quickRow, *contentRow;
+  GCSFieldExtractor   *extractor;
+  NSException         *error;
+  NSNumber            *storedVersion;
+  BOOL                isNewRecord, hasInsertDelegate, hasUpdateDelegate;
+  NSCalendarDate      *nowDate;
+  NSNumber            *now;
+
+  /* check preconditions */  
+  if (_name == nil) {
+    return [NSException exceptionWithName:@"GCSStoreException"
+                       reason:@"no content filename was provided"
+                       userInfo:nil];
+  }
+  if (_content == nil) {
+    return [NSException exceptionWithName:@"GCSStoreException"
+                       reason:@"no content was provided"
+                       userInfo:nil];
+  }
+  
+  /* run */
+  error   = nil;
+  nowDate = [NSCalendarDate date];
+  now     = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
+  
+  if (doLogStore)
+    [self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
+  
+  storedVersion = [self versionOfContentWithName:_name];
+  if (doLogStore)
+    [self logWithFormat:@"  version: %@", storedVersion];
+  isNewRecord = [storedVersion isNotNull] ? NO : YES;
+  
+  /* check whether sequence matches */  
+  if (_baseVersion != 0 /* use 0 to override check */) {
+    if (_baseVersion != [storedVersion unsignedIntValue]) {
+      /* version mismatch (concurrent update) */
+      return [self errorVersionMismatchBetweenStoredVersion:
+                    [storedVersion unsignedIntValue]
+                  andExpectedVersion:_baseVersion];
+    }
+  }
+  
+  /* extract quick info */
+  extractor = [self->folderInfo quickExtractor];
+  if ((quickRow = [extractor extractQuickFieldsFromContent:_content]) == nil) {
+    return [self errorExtractorReturnedNoQuickRow:extractor
+                forContent:_content];
+  }
+  
+  [quickRow setObject:_name forKey:@"c_name"];
+  
+  if (doLogStore)
+    [self logWithFormat:@"  store quick: %@", quickRow];
+  
+  /* make content row */
+  contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
+  
+  if (self->ofFlags.sameTableForQuick)
+    [contentRow addEntriesFromDictionary:quickRow];
+  
+  [contentRow setObject:_name forKey:@"c_name"];
+  if (isNewRecord) [contentRow setObject:now forKey:@"c_creationdate"];
+  [contentRow setObject:now forKey:@"c_lastmodified"];
+  if (isNewRecord)
+    [contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"];
+  else {
+    // TODO: increase version?
+    [contentRow setObject:
+                 [NSNumber numberWithInt:([storedVersion intValue] + 1)]
+               forKey:@"c_version"];
+  }
+  [contentRow setObject:_content forKey:@"c_content"];
+  
+  /* open channels */
+  if ((storeChannel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@"%s: could not open storage channel!",
+           __PRETTY_FUNCTION__];
+    return nil;
+  }
+  if (!self->ofFlags.sameTableForQuick) {
+    if ((quickChannel = [self acquireQuickChannel]) == nil) {
+      [self errorWithFormat:@"%s: could not open quick channel!",
+             __PRETTY_FUNCTION__];
+      [self releaseChannel:storeChannel];
+      return nil;
+    }
+  }
+
+  /* we check if we can call directly methods on our adaptor
+     channel delegate. If not, we generate SQL ourself since it'll
+     be a little bit faster and less complex than using GDL to do so */
+  hasInsertDelegate = [[storeChannel delegate] 
+                       respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
+  hasUpdateDelegate = [[storeChannel delegate]
+                       respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
+
+  [[storeChannel adaptorContext] beginTransaction];
+  [[quickChannel adaptorContext] beginTransaction];
+  
+  if (isNewRecord) {
+    if (!self->ofFlags.sameTableForQuick) {
+       error = (hasInsertDelegate ? [quickChannel insertRowX: quickRow
+                                                  forEntity: [self _entityWithName: [self quickTableName]]]
+                : [quickChannel evaluateExpressionX: [self _generateInsertStatementForRow: quickRow 
+                                                           tableName: [self quickTableName]]]);
+      CHECKERROR();
+    }
+
+    error = (hasInsertDelegate ? [storeChannel insertRowX: contentRow
+                                              forEntity: [self _entityWithName: [self storeTableName]]]
+            : [storeChannel evaluateExpressionX: [self _generateInsertStatementForRow: contentRow
+                                                       tableName: [self storeTableName]]]);
+
+    CHECKERROR();
+  }
+  else {
+    if (!self->ofFlags.sameTableForQuick) {
+      error = (hasUpdateDelegate ? [quickChannel updateRowX: quickRow
+                                                describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name"
+                                                                            isEqualTo: _name  andColumn: nil  isEqualTo: nil
+                                                                            entity: [self _entityWithName: [self quickTableName]]]]
+              : [quickChannel evaluateExpressionX: [self _generateUpdateStatementForRow: quickRow
+                                                         tableName: [self quickTableName]
+                                                         whereColumn: @"c_name" isEqualTo: _name
+                                                         andColumn: nil isEqualTo: nil]]);
+      CHECKERROR();
+    }
+    
+    error = (hasUpdateDelegate ? [storeChannel updateRowX: contentRow
+                                              describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name"  isEqualTo: _name
+                                                                          andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
+                                                                          isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt:_baseVersion] : (NSNumber *)nil)
+                                                                          entity: [self _entityWithName: [self storeTableName]]]]
+            : [storeChannel evaluateExpressionX: [self _generateUpdateStatementForRow: contentRow  tableName:[self storeTableName]
+                                                       whereColumn: @"c_name"  isEqualTo: _name
+                                                       andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
+                                                       isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: _baseVersion] : (NSNumber *)nil)]]);
+    CHECKERROR();
+  }
+  
+  [[storeChannel adaptorContext] commitTransaction];
+  [[quickChannel adaptorContext] commitTransaction];
+  
+  [self releaseChannel: storeChannel];
+  if (!self->ofFlags.sameTableForQuick) [self releaseChannel: quickChannel];
+
+  return error;
+}
+
+
+- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name {
+  /* this method does not check for concurrent writes */
+  return [self writeContent:_content toName:_name baseVersion:0];
+}
+
+- (NSException *)deleteContentWithName:(NSString *)_name {
+  EOAdaptorChannel *storeChannel, *quickChannel;
+  NSException *error;
+  NSString *delsql;
+  NSCalendarDate *nowDate;
+  
+  /* check preconditions */
+  
+  if (_name == nil) {
+    return [NSException exceptionWithName:@"GCSDeleteException"
+                       reason:@"no content filename was provided"
+                       userInfo:nil];
+  }
+  
+  if (doLogStore)
+    [self logWithFormat:@"should delete content: '%@'", _name];
+  
+  /* open channels */
+  
+  if ((storeChannel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@"could not open storage channel!"];
+    return nil;
+  }
+  if (!self->ofFlags.sameTableForQuick) {
+    if ((quickChannel = [self acquireQuickChannel]) == nil) {
+      [self errorWithFormat:@"could not open quick channel!"];
+      [self releaseChannel:storeChannel];
+      return nil;
+    }
+  }
+  
+  /* delete rows */
+  nowDate = [NSCalendarDate calendarDate];
+
+  delsql = [@"UPDATE " stringByAppendingString:[self storeTableName]];
+  delsql = [delsql stringByAppendingString:@" SET c_deleted = 1"];
+  delsql = [delsql stringByAppendingFormat:@", c_lastmodified = %u",
+                  (unsigned int) [nowDate timeIntervalSince1970]];
+  delsql = [delsql stringByAppendingString:@" WHERE c_name="];
+  delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
+  if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
+    [self errorWithFormat:
+           @"%s: cannot delete content '%@': %@", 
+         __PRETTY_FUNCTION__, delsql, error];
+  }
+  else if (!self->ofFlags.sameTableForQuick) {
+    /* content row deleted, now delete the quick row */
+    delsql = [@"DELETE FROM " stringByAppendingString:[self quickTableName]];
+    delsql = [delsql stringByAppendingString:@" WHERE c_name="];
+    delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
+    if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
+      [self errorWithFormat:
+             @"%s: cannot delete quick row '%@': %@", 
+           __PRETTY_FUNCTION__, delsql, error];
+      /* 
+        Note: we now have a "broken" record, needs to be periodically GCed by
+              a script!
+      */
+    }
+  }
+  
+  /* release channels and return */
+  
+  [self releaseChannel:storeChannel];
+  if (!self->ofFlags.sameTableForQuick)
+    [self releaseChannel:quickChannel];
+  return error;
+}
+
+- (NSException *)deleteFolder {
+  EOAdaptorChannel *channel;
+  NSString *delsql;
+  NSString *table;
+  
+  /* open channels */
+  
+  if ((channel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@"could not open channel!"];
+    return nil;
+  }
+  
+  /* delete rows */
+
+  table = [self storeTableName];
+  if ([table length] > 0) {
+    delsql = [@"DROP TABLE " stringByAppendingString: table];
+    [channel evaluateExpressionX:delsql];
+  }
+  table = [self quickTableName];
+  if ([table length] > 0) {
+    delsql = [@"DROP TABLE " stringByAppendingString: table];
+    [channel evaluateExpressionX:delsql];
+  }
+  table = [self aclTableName];
+  if ([table length] > 0) {
+    delsql = [@"DROP TABLE  " stringByAppendingString: table];
+    [channel evaluateExpressionX:delsql];
+  }
+  
+  [self releaseChannel:channel];
+
+  return nil;
+}
+
+- (NSString *)columnNameForFieldName:(NSString *)_fieldName {
+  return _fieldName;
+}
+
+/* SQL generation */
+
+- (NSString *)generateSQLForSortOrderings:(NSArray *)_so {
+  NSMutableString *sql;
+  unsigned i, count;
+
+  if ((count = [_so count]) == 0)
+    return nil;
+  
+  sql = [NSMutableString stringWithCapacity:(count * 16)];
+  for (i = 0; i < count; i++) {
+    EOSortOrdering *so;
+    NSString *column;
+    SEL      sel;
+    
+    so     = [_so objectAtIndex:i];
+    sel    = [so selector];
+    column = [self columnNameForFieldName:[so key]];
+    
+    if (i > 0) [sql appendString:@", "];
+    
+    if (sel_eq(sel, EOCompareAscending)) {
+      [sql appendString:column];
+      [sql appendString:@" ASC"];
+    }
+    else if (sel_eq(sel, EOCompareDescending)) {
+      [sql appendString:column];
+      [sql appendString:@" DESC"];
+    }
+    else if (sel_eq(sel, EOCompareCaseInsensitiveAscending)) {
+      [sql appendString:@"UPPER("];
+      [sql appendString:column];
+      [sql appendString:@") ASC"];
+    }
+    else if (sel_eq(sel, EOCompareCaseInsensitiveDescending)) {
+      [sql appendString:@"UPPER("];
+      [sql appendString:column];
+      [sql appendString:@") DESC"];
+    }
+    else {
+      [self logWithFormat:@"cannot handle sort selector in store: %@",
+             NSStringFromSelector(sel)];
+    }
+  }
+  return sql;
+}
+
+- (NSString *)generateSQLForQualifier:(EOQualifier *)_q {
+  NSMutableString *ms;
+  
+  if (_q == nil) return nil;
+  ms = [NSMutableString stringWithCapacity:32];
+  [_q _gcsAppendToString:ms];
+  return ms;
+}
+
+/* fetching */
+
+- (NSArray *)fetchFields:(NSArray *)_flds 
+  fetchSpecification:(EOFetchSpecification *)_fs
+{
+  EOQualifier      *qualifier;
+  NSArray          *sortOrderings;
+  EOAdaptorChannel *channel;
+  NSException      *error;
+  NSMutableString  *sql;
+  NSArray          *attrs;
+  NSMutableArray   *results;
+  NSDictionary     *row;
+  
+  qualifier     = [_fs qualifier];
+  sortOrderings = [_fs sortOrderings];
+  
+#if 0
+  [self logWithFormat:@"FETCH: %@", _flds];
+  [self logWithFormat:@"  MATCH: %@", _q];
+#endif
+  
+  /* generate SQL */
+
+  sql = [NSMutableString stringWithCapacity:256];
+  [sql appendString:@"SELECT "];
+  if (_flds == nil)
+    [sql appendString:@"*"];
+  else {
+    unsigned i, count;
+    
+    count = [_flds count];
+    for (i = 0; i < count; i++) {
+      if (i > 0) [sql appendString:@", "];
+      [sql appendString:[self columnNameForFieldName:[_flds objectAtIndex:i]]];
+    }
+  }
+  [sql appendString:@" FROM "];
+  [sql appendString:[self quickTableName]];
+  
+  if (qualifier != nil) {
+    [sql appendString:@" WHERE "];
+    [sql appendString:[self generateSQLForQualifier:qualifier]];
+  }
+  if ([sortOrderings count] > 0) {
+    [sql appendString:@" ORDER BY "];
+    [sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
+  }
+#if 0
+  /* limit */
+  [sql appendString:@" LIMIT "]; // count
+  [sql appendString:@" OFFSET "]; // index from 0
+#endif
+  
+  /* open channel */
+
+  if ((channel = [self acquireStoreChannel]) == nil) {
+    [self errorWithFormat:@" could not open storage channel!"];
+    return nil;
+  }
+  
+  /* run SQL */
+
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    [self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@", 
+           __PRETTY_FUNCTION__, sql, error];
+    [self releaseChannel:channel];
+    return nil;
+  }
+  
+  /* fetch results */
+  
+  results = [NSMutableArray arrayWithCapacity:64];
+  attrs   = [channel describeResults:NO /* do not beautify names */];
+  while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
+    [results addObject:row];
+  
+  /* release channels */
+  
+  [self releaseChannel:channel];
+  
+  return results;
+}
+- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q {
+  EOFetchSpecification *fs;
+
+  if (_q == nil)
+    fs = nil;
+  else {
+    fs = [EOFetchSpecification fetchSpecificationWithEntityName:
+                                [self folderName]
+                              qualifier:_q
+                              sortOrderings:nil];
+  }
+  return [self fetchFields:_flds fetchSpecification:fs];
+}
+
+- (NSArray *)fetchAclWithSpecification:(EOFetchSpecification *)_fs {
+  EOQualifier      *qualifier;
+  NSArray          *sortOrderings;
+  EOAdaptorChannel *channel;
+  NSException      *error;
+  NSMutableString  *sql;
+  NSArray          *attrs;
+  NSMutableArray   *results;
+  NSDictionary     *row;
+  
+  qualifier     = [_fs qualifier];
+  sortOrderings = [_fs sortOrderings];
+  
+#if 0
+  [self logWithFormat:@"FETCH: %@", _flds];
+  [self logWithFormat:@"  MATCH: %@", _q];
+#endif
+  
+  /* generate SQL */
+
+  sql = [NSMutableString stringWithCapacity:256];
+  [sql appendString:@"SELECT c_uid, c_object, c_role"];
+  [sql appendString:@" FROM "];
+  [sql appendString:[self aclTableName]];
+  
+  if (qualifier != nil) {
+    [sql appendString:@" WHERE "];
+    [sql appendString:[self generateSQLForQualifier:qualifier]];
+  }
+  if ([sortOrderings count] > 0) {
+    [sql appendString:@" ORDER BY "];
+    [sql appendString:[self generateSQLForSortOrderings:sortOrderings]];
+  }
+#if 0
+  /* limit */
+  [sql appendString:@" LIMIT "]; // count
+  [sql appendString:@" OFFSET "]; // index from 0
+#endif
+  
+  /* open channel */
+
+  if ((channel = [self acquireAclChannel]) == nil) {
+    [self errorWithFormat:@"could not open acl channel!"];
+    return nil;
+  }
+  
+  /* run SQL */
+
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", 
+           __PRETTY_FUNCTION__, sql, error];
+    [self releaseChannel:channel];
+    return nil;
+  }
+  
+  /* fetch results */
+  
+  results = [NSMutableArray arrayWithCapacity:64];
+  attrs   = [channel describeResults:NO /* do not beautify names */];
+  while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
+    [results addObject:row];
+  
+  /* release channels */
+  
+  [self releaseChannel:channel];
+  
+  return results;
+}
+- (NSArray *) fetchAclMatchingQualifier:(EOQualifier *)_q {
+  EOFetchSpecification *fs;
+
+  if (_q == nil)
+    fs = nil;
+  else {
+    fs = [EOFetchSpecification fetchSpecificationWithEntityName:
+                                [self folderName]
+                              qualifier:_q
+                              sortOrderings:nil];
+  }
+  return [self fetchAclWithSpecification:fs];
+}
+
+- (void) deleteAclMatchingQualifier:(EOQualifier *)_q {
+  EOFetchSpecification *fs;
+
+  if (_q != nil) {
+    fs = [EOFetchSpecification fetchSpecificationWithEntityName:
+                                [self folderName]
+                              qualifier:_q
+                              sortOrderings:nil];
+    [self deleteAclWithSpecification:fs];
+  }
+}
+
+- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs
+{
+  EOQualifier      *qualifier;
+  EOAdaptorChannel *channel;
+  NSException      *error;
+  NSMutableString  *sql;
+  
+  qualifier     = [_fs qualifier];
+  if (qualifier != nil) {
+    sql = [NSMutableString stringWithCapacity:256];
+    [sql appendString:@"DELETE FROM "];
+    [sql appendString:[self aclTableName]];
+    [sql appendString:@" WHERE "];
+    [sql appendString:[self generateSQLForQualifier:qualifier]];
+  }
+  
+  /* open channel */
+
+  if ((channel = [self acquireAclChannel]) == nil) {
+    [self errorWithFormat:@"could not open acl channel!"];
+    return;
+  }
+  
+  /* run SQL */
+
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", 
+           __PRETTY_FUNCTION__, sql, error];
+    [self releaseChannel:channel];
+    return;
+  }
+  
+  [self releaseChannel:channel];
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+  id tmp;
+  
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+
+  if (self->folderId)
+    [ms appendFormat:@" id=%@", self->folderId];
+  else
+    [ms appendString:@" no-id"];
+
+  if ((tmp = [self path]))           [ms appendFormat:@" path=%@", tmp];
+  if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
+  if ((tmp = [self location]))
+    [ms appendFormat:@" loc=%@", [tmp absoluteString]];
+  
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSFolder */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.h
new file mode 100644 (file)
index 0000000..27aa09a
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSFolderManager_H__
+#define __GDLContentStore_GCSFolderManager_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  GCSFolderManager
+  
+  Objects of this class manage the "folder_info" table, they manage the
+  model and manage the tables required for a folder.
+*/
+
+@class NSString, NSArray, NSURL, NSDictionary, NSException;
+@class GCSChannelManager, GCSFolder, GCSFolderType;
+
+@interface GCSFolderManager : NSObject
+{
+  GCSChannelManager *channelManager;
+  NSDictionary      *nameToType;
+  NSURL             *folderInfoLocation;
+}
+
++ (id)defaultFolderManager;
+- (id)initWithFolderInfoLocation:(NSURL *)_url;
+
+/* accessors */
+
+- (NSURL *)folderInfoLocation;
+- (NSString *)folderInfoTableName;
+
+/* connection */
+
+- (GCSChannelManager *)channelManager;
+- (BOOL)canConnect;
+
+/* handling folder names */
+
+- (NSString *)internalNameFromPath:(NSString *)_path;
+- (NSArray *)internalNamesFromPath:(NSString *)_path;
+- (NSString *)pathFromInternalName:(NSString *)_name;
+
+/* operations */
+
+- (BOOL)folderExistsAtPath:(NSString *)_path;
+- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag;
+
+- (GCSFolder *)folderAtPath:(NSString *)_path;
+
+- (NSException *)createFolderOfType:(NSString *)_type withName:(NSString *)_name atPath:(NSString *)_path;
+- (NSException *)deleteFolderAtPath:(NSString *)_path;
+
+/* folder types */
+
+- (GCSFolderType *)folderTypeWithName:(NSString *)_name;
+
+/* cache management */
+
+- (void)reset;
+
+@end
+
+#endif /* __GDLContentStore_GCSFolderManager_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolderManager.m
new file mode 100644 (file)
index 0000000..3526fd1
--- /dev/null
@@ -0,0 +1,853 @@
+/*
+  Copyright (C) 2004-2007 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSFolderManager.h"
+#include "GCSChannelManager.h"
+#include "GCSFolderType.h"
+#include "GCSFolder.h"
+#include "NSURL+GCS.h"
+#include "EOAdaptorChannel+GCS.h"
+#include "common.h"
+#include <GDLAccess/EOAdaptorChannel.h>
+#include <NGExtensions/NGResourceLocator.h>
+#include <unistd.h>
+
+/*
+  Required database schema:
+  
+    <arbitary table>
+      c_path
+      c_path1, path2, path3... [quickPathCount times]
+      c_foldername
+  
+  TODO:
+  - add a local cache?
+*/
+
+@implementation GCSFolderManager
+
+static GCSFolderManager *fm = nil;
+static BOOL       debugOn                   = NO;
+static BOOL       debugSQLGen               = NO;
+static BOOL       debugPathTraversal        = NO;
+static int        quickPathCount            = 4;
+static NSArray    *emptyArray               = nil;
+#if 0
+static NSString   *GCSPathColumnName        = @"c_path";
+static NSString   *GCSTypeColumnName        = @"c_folder_type";
+static NSString   *GCSTypeRecordName        = @"c_folder_type";
+#endif
+static NSString   *GCSPathRecordName        = @"c_path";
+static NSString   *GCSGenericFolderTypeName = @"Container";
+static const char *GCSPathColumnPattern     = "c_path%i";
+static NSCharacterSet *asciiAlphaNumericCS  = nil;
+
++ (void)initialize {
+  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+  
+  debugOn     = [ud boolForKey:@"GCSFolderManagerDebugEnabled"];
+  debugSQLGen = [ud boolForKey:@"GCSFolderManagerSQLDebugEnabled"];
+  emptyArray  = [[NSArray alloc] init];
+  if (!asciiAlphaNumericCS)
+    {
+      asciiAlphaNumericCS
+       = [NSCharacterSet characterSetWithCharactersInString:
+                           @"0123456789"
+                         @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                         @"abcdefghijklmnopqrstuvwxyz"];
+      [asciiAlphaNumericCS retain];
+    }
+}
+
++ (id)defaultFolderManager {
+  NSString *s;
+  NSURL    *url;
+  if (fm) return fm;
+  
+  s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSFolderInfoURL"];
+  if ([s length] == 0) {
+    NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not configured.",
+         __PRETTY_FUNCTION__);
+    return nil;
+  }
+  if ((url = [NSURL URLWithString:s]) == nil) {
+    NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not a valid URL: '%@'",
+         __PRETTY_FUNCTION__, s);
+    return nil;
+  }
+  if ((fm = [[self alloc] initWithFolderInfoLocation:url]) == nil) {
+    NSLog(@"ERROR(%s): could not create folder manager with URL: '%@'",
+         __PRETTY_FUNCTION__, [url absoluteString]);
+    return nil;
+  }
+  
+  NSLog(@"Note: setup default manager at: %@", url);
+  return fm;
+}
+
+- (NSDictionary *)loadDefaultFolderTypes {
+  NSMutableDictionary *typeMap;
+  NSArray  *types;
+  unsigned i, count;
+  
+  
+  types = [[GCSFolderType resourceLocator] lookupAllFilesWithExtension:@"ocs"
+                                          doReturnFullPath:NO];
+  if ((count = [types count]) == 0) {
+    [self logWithFormat:@"Note: no GCS folder types found."];
+    return nil;
+  }
+  
+  typeMap = [NSMutableDictionary dictionaryWithCapacity:count];
+  
+  [self logWithFormat:@"Note: loading %d GCS folder types:", count];
+  for (i = 0, count = [types count]; i < count; i++) {
+    NSString *type;
+    GCSFolderType *typeObject;
+    
+    type       = [[types objectAtIndex:i] stringByDeletingPathExtension];
+    typeObject = [[GCSFolderType alloc] initWithFolderTypeName:type];
+    
+    [self logWithFormat:@"  %@: %s", 
+           type, [typeObject isNotNull] ? "OK" : "FAIL"];
+    [typeMap setObject:typeObject forKey:type];
+    [typeObject release];
+  }
+  
+  return typeMap;
+}
+
+- (id)initWithFolderInfoLocation:(NSURL *)_url {
+  if (_url == nil) {
+    [self logWithFormat:@"ERROR(%s): missing folder info url!", 
+           __PRETTY_FUNCTION__];
+    [self release];
+    return nil;
+  }
+  if ((self = [super init])) {
+    self->channelManager = [[GCSChannelManager defaultChannelManager] retain];
+    self->folderInfoLocation = [_url retain];
+
+    if ([[self folderInfoTableName] length] == 0) {
+      [self logWithFormat:@"ERROR(%s): missing tablename in URL: %@", 
+            __PRETTY_FUNCTION__, [_url absoluteString]];
+      [self release];
+      return nil;
+    }
+    
+    /* register default folder types */
+    self->nameToType = [[self loadDefaultFolderTypes] copy];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  [self->nameToType         release];
+  [self->folderInfoLocation release];
+  [self->channelManager     release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (NSURL *)folderInfoLocation {
+  return self->folderInfoLocation;
+}
+
+- (NSString *)folderInfoTableName {
+  return [[self folderInfoLocation] gcsTableName];
+}
+
+/* connection */
+
+- (GCSChannelManager *)channelManager {
+  return self->channelManager;
+}
+
+- (EOAdaptorChannel *)acquireOpenChannel {
+  EOAdaptorChannel *ch;
+  
+  ch = [[self channelManager] acquireOpenChannelForURL:
+             [self folderInfoLocation]];
+  return ch;
+}
+- (void)releaseChannel:(EOAdaptorChannel *)_channel {
+  [[self channelManager] releaseChannel:_channel];
+  if (debugOn) [self debugWithFormat:@"released channel: %@", _channel];
+}
+
+- (BOOL)canConnect {
+  return [[self channelManager] canConnect:[self folderInfoLocation]];
+}
+
+- (NSArray *)performSQL:(NSString *)_sql {
+  EOAdaptorChannel *channel;
+  NSException    *ex;
+  NSMutableArray *rows;
+  NSDictionary   *row;
+  NSArray        *attrs;
+  
+  /* acquire channel */
+  
+  if ((channel = [self acquireOpenChannel]) == nil) {
+    if (debugOn) [self debugWithFormat:@"could not acquire channel!"];
+    return nil;
+  }
+  if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
+  
+  /* run SQL */
+  
+  if ((ex = [channel evaluateExpressionX:_sql]) != nil) {
+    [self logWithFormat:@"ERROR(%s): cannot execute\n  SQL '%@':\n  %@", 
+           __PRETTY_FUNCTION__, _sql, ex];
+    [self releaseChannel:channel];
+    return nil;
+  }
+  
+  /* fetch results */
+  
+  attrs = [channel describeResults:NO /* do not beautify names */];
+  rows = [NSMutableArray arrayWithCapacity:16];
+  while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
+    [rows addObject:row];
+  
+  [self releaseChannel:channel];
+  return rows;
+}
+
+/* row factory */
+
+- (GCSFolder *)folderForRecord:(NSDictionary *)_record {
+  GCSFolder     *folder;
+  GCSFolderType *folderType;
+  NSString      *folderTypeName, *locationString, *folderName, *path;
+  NSNumber      *folderId;
+  NSURL         *location, *quickLocation, *aclLocation;
+  
+  if (_record == nil) return nil;
+  
+  folderTypeName = [_record objectForKey:@"c_folder_type"];
+  if (![folderTypeName isNotNull]) {
+    [self logWithFormat:@"ERROR(%s): missing type in folder: %@",
+           __PRETTY_FUNCTION__, _record];
+    return nil;
+  }
+  if ((folderType = [self folderTypeWithName:folderTypeName]) == nil) {
+    [self logWithFormat:
+           @"ERROR(%s): could not resolve type '%@' of folder: %@",
+           __PRETTY_FUNCTION__,
+           folderTypeName, [_record valueForKey:@"c_path"]];
+    return nil;
+  }
+  
+  folderId   = [_record objectForKey:@"c_folder_id"];
+  folderName = [_record objectForKey:@"c_path"];
+  path       = [self pathFromInternalName:folderName];
+  
+  locationString = [_record objectForKey:@"c_location"];
+  location = [locationString isNotNull] 
+    ? [NSURL URLWithString:locationString]
+    : nil;
+  if (location == nil) {
+    [self logWithFormat:@"ERROR(%s): missing folder location in record: %@", 
+           __PRETTY_FUNCTION__, _record];
+    return nil;
+  }
+  
+  locationString = [_record objectForKey:@"c_quick_location"];
+  quickLocation = [locationString isNotNull] 
+    ? [NSURL URLWithString:locationString]
+    : nil;
+
+  if (quickLocation == nil) {
+    [self logWithFormat:@"WARNING(%s): missing quick location in record: %@", 
+           __PRETTY_FUNCTION__, _record];
+  }
+
+  locationString = [_record objectForKey:@"c_acl_location"];
+  aclLocation = [locationString isNotNull]
+    ? [NSURL URLWithString:locationString]
+    : nil;
+  
+  folder = [[GCSFolder alloc] initWithPath:path primaryKey:folderId
+                             folderTypeName:folderTypeName 
+                             folderType:folderType
+                             location:location quickLocation:quickLocation
+                              aclLocation:aclLocation
+                             folderManager:self];
+  return [folder autorelease];
+}
+
+/* path SQL */
+
+- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
+  exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
+{
+  /* generates a WHERE qualifier for matching the "quick" entries */
+  NSMutableString *sql;
+  unsigned i, count;
+  
+  if ((count = [_names count]) == 0) {
+    [self debugWithFormat:@"WARNING(%s): passed in empty name array!",
+           __PRETTY_FUNCTION__];
+    return @"1 = 2";
+  }
+  
+  sql = [NSMutableString stringWithCapacity:(count * 8)];
+  for (i = 0; i < quickPathCount; i++) {
+    NSString *pathColumn;
+    char buf[32];
+    
+    sprintf(buf, GCSPathColumnPattern, (i + 1));
+    pathColumn = [[NSString alloc] initWithCString:buf];
+    
+    /* Note: the AND addition must be inside the if's for non-exact stuff */
+    
+    if (i < count) {
+      /* exact match, regular column */
+      if ([sql length] > 0) [sql appendString:@" AND "];
+      [sql appendString:pathColumn];
+      [sql appendFormat:@" = '%@'", [_names objectAtIndex:i]];
+    }
+    else if (_beExact) {
+      /* exact match, ensure that all additional quick-cols are NULL */
+      if ([sql length] > 0) [sql appendString:@" AND "];
+      [sql appendString:pathColumn];
+      [sql appendString:@" IS NULL"];
+      if (debugPathTraversal) [self logWithFormat:@"BE EXACT, NULL columns"];
+    }
+    else if (_directSubs) {
+      /* fetch immediate subfolders */
+      if ([sql length] > 0) [sql appendString:@" AND "];
+      [sql appendString:pathColumn];
+      if (i == count) {
+       /* if it is a direct subfolder, the next path cannot be empty */
+       [sql appendString:@" IS NOT NULL"];
+       if (debugPathTraversal)
+         [self logWithFormat:@"DIRECT SUBS, first level"];
+      }
+      else {
+       /* but for 'direct' subfolders, all following things must be empty */
+       [sql appendString:@" IS NULL"];
+       if (debugPathTraversal) 
+         [self logWithFormat:@"DIRECT SUBS, lower level"];
+      }
+    }
+    
+    [pathColumn release];
+  }
+  
+  if (_beExact && (count > quickPathCount)) {
+    [sql appendString:@" AND c_foldername = '"];
+    [sql appendString:[_names lastObject]];
+    [sql appendString:@"'"];
+  }
+  
+  return sql;
+}
+
+- (NSString *)generateSQLPathFetchForInternalNames:(NSArray *)_names
+  exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
+{
+  /* fetches the 'path' subset for a given quick-names */
+  NSMutableString *sql;
+  NSString *ws;
+  
+  ws = [self generateSQLWhereForInternalNames:_names 
+            exactMatch:_beExact orDirectSubfolderMatch:_directSubs];
+  if ([ws length] == 0)
+    return nil;
+  
+  sql = [NSMutableString stringWithCapacity:256];
+  [sql appendString:@"SELECT c_path FROM "];
+  [sql appendString:[self folderInfoTableName]];
+  [sql appendString:@" WHERE "];
+  [sql appendString:ws];
+  if (debugSQLGen) [self logWithFormat:@"PathFetch-SQL: %@", sql];
+  return sql;
+}
+
+/* handling folder names */
+
+- (BOOL)_isStandardizedPath:(NSString *)_path {
+  if (![_path isAbsolutePath])                return NO;
+  if ([_path rangeOfString:@".."].length > 0) return NO;
+  if ([_path rangeOfString:@"~"].length  > 0) return NO;
+  if ([_path rangeOfString:@"//"].length > 0) return NO;
+  return YES;
+}
+
+- (NSString *)internalNameFromPath:(NSString *)_path {
+  // TODO: ensure proper path and SQL escaping!
+  
+  if (![self _isStandardizedPath:_path]) {
+    [self debugWithFormat:@"%s: not a standardized path: '%@'", 
+           __PRETTY_FUNCTION__, _path];
+    return nil;
+  }
+  
+  if ([_path hasSuffix:@"/"] && [_path length] > 1)
+    _path = [_path substringToIndex:([_path length] - 1)];
+  
+  return _path;
+}
+- (NSArray *)internalNamesFromPath:(NSString *)_path {
+  NSString *fname;
+  NSArray  *fnames;
+  
+  if ((fname = [self internalNameFromPath:_path]) == nil)
+    return nil;
+  
+  if ([fname hasPrefix:@"/"])
+    fname = [fname substringFromIndex:1];
+  
+  fnames = [fname componentsSeparatedByString:@"/"];
+  if ([fnames count] == 0)
+    return nil;
+  
+  return fnames;
+}
+- (NSString *)pathFromInternalName:(NSString *)_name {
+  /* for incomplete pathes, like '/Users/helge/' */
+  return _name;
+}
+- (NSString *)pathPartFromInternalName:(NSString *)_name {
+  /* for incomplete pathes, like 'Users/' */
+  return _name;
+}
+
+- (NSDictionary *)filterRecords:(NSArray *)_records forPath:(NSString *)_path {
+  unsigned i, count;
+  NSString *name;
+  
+  if (_records == nil) return nil;
+  if ((name = [self internalNameFromPath:_path]) == nil) return nil;
+  
+  for (i = 0, count = [_records count]; i < count; i++) {
+    NSDictionary *record;
+    NSString     *recName;
+    
+    record  = [_records objectAtIndex:i];
+    recName = [record objectForKey:GCSPathRecordName];
+#if 0
+    [self logWithFormat:@"check '%@' vs '%@' (%@)...", 
+         name, recName, [_records objectAtIndex:i]];
+#endif
+    
+    if ([name isEqualToString:recName])
+      return [_records objectAtIndex:i];
+  }
+  return nil;
+}
+
+- (BOOL)folderExistsAtPath:(NSString *)_path {
+  NSString *fname;
+  NSArray  *fnames, *records;
+  NSString *sql;
+  unsigned count;
+  
+  if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+    [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+    return NO;
+  }
+  
+  sql = [self generateSQLPathFetchForInternalNames:fnames 
+             exactMatch:YES orDirectSubfolderMatch:NO];
+  if ([sql length] == 0) {
+    [self debugWithFormat:@"got no SQL for names: %@", fnames];
+    return NO;
+  }
+  
+  if ((records = [self performSQL:sql]) == nil) {
+    [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", 
+           __PRETTY_FUNCTION__, sql];
+    return NO;
+  }
+  
+  if ((count = [records count]) == 0)
+    return NO;
+  
+  fname = [self internalNameFromPath:_path];
+  if (count == 1) {
+    NSDictionary *record;
+    NSString *sname;
+    
+    record = [records objectAtIndex:0];
+    sname  = [record objectForKey:GCSPathRecordName];
+    return [fname isEqualToString:sname];
+  }
+  
+  [self logWithFormat:@"records: %@", records];
+  
+  return NO;
+}
+
+- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_recursive{
+  NSMutableArray *result;
+  NSString *fname;
+  NSArray  *fnames, *records;
+  NSString *sql;
+  unsigned i, count;
+  
+  if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+    [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+    return nil;
+  }
+  
+  sql = [self generateSQLPathFetchForInternalNames:fnames 
+             exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)];
+  if ([sql length] == 0) {
+    [self debugWithFormat:@"got no SQL for names: %@", fnames];
+    return nil;
+  }
+  
+  if ((records = [self performSQL:sql]) == nil) {
+    [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", 
+           __PRETTY_FUNCTION__, sql];
+    return nil;
+  }
+  
+  if ((count = [records count]) == 0)
+    return emptyArray;
+
+  result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)];
+  
+  fname = [self internalNameFromPath:_path];
+  fname = [fname stringByAppendingString:@"/"]; /* add slash */
+  for (i = 0; i < count; i++) {
+    NSDictionary *record;
+    NSString *sname, *spath;
+    
+    record = [records objectAtIndex:i];
+    sname  = [record objectForKey:GCSPathRecordName];
+    if (![sname hasPrefix:fname]) /* does not match at all ... */
+      continue;
+    
+    /* strip prefix and following slash */
+    sname = [sname substringFromIndex:[fname length]];
+    spath = [self pathPartFromInternalName:sname];
+    
+    if (_recursive) {
+      if ([spath length] > 0) [result addObject:spath];
+    }
+    else {
+      /* direct children only, so exclude everything with a slash */
+      if ([sname rangeOfString:@"/"].length == 0 && [spath length] > 0)
+       [result addObject:spath];
+    }
+  }
+  
+  return result;
+}
+
+- (GCSFolder *)folderAtPath:(NSString *)_path {
+  NSMutableString *sql;
+  NSArray      *fnames, *records;
+  NSString     *ws;
+  NSDictionary *record;
+  
+  if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+    [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+    return nil;
+  }
+  
+  /* generate SQL to fetch folder attributes */
+  
+  ws = [self generateSQLWhereForInternalNames:fnames 
+            exactMatch:YES orDirectSubfolderMatch:NO];
+  
+  sql = [NSMutableString stringWithCapacity:256];
+  [sql appendString:@"SELECT "];
+  [sql appendString:@"c_folder_id, "];
+  [sql appendString:@"c_path, "];
+  [sql appendString:@"c_location, c_quick_location, c_acl_location,"];
+  [sql appendString:@" c_folder_type"];
+  [sql appendString:@" FROM "];
+  [sql appendString:[self folderInfoTableName]];
+  [sql appendString:@" WHERE "];
+  [sql appendString:ws];
+  
+  if (debugSQLGen) [self logWithFormat:@"folderAtPath: %@", sql];
+
+  /* fetching */
+  
+  if ((records = [self performSQL:sql]) == nil) {
+    [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", 
+           __PRETTY_FUNCTION__, sql];
+    return nil;
+  }
+  
+  // TODO: need to filter on path
+  //         required when we start to have deeper hierarchies
+  //       => isn't that already done below?
+  
+  if ([records count] != 1) {
+    if ([records count] == 0) {
+      [self debugWithFormat:@"found no records for path: '%@'", _path];
+      return nil;
+    }
+    
+    [self logWithFormat:@"ERROR(%s): more than one row for path: '%@'", 
+           __PRETTY_FUNCTION__, _path];
+    return nil;
+  }
+  
+  if ((record = [self filterRecords:records forPath:_path]) == nil) {
+    [self debugWithFormat:@"found no record for path: '%@'", _path];
+    return nil;
+  }
+  
+  return [self folderForRecord:record];
+}
+
+- (NSString *)baseTableNameWithUID:(NSString *)_uid {
+  NSDate *now;
+  unichar currentChar;
+  unsigned int count, max, done;
+  NSMutableString *newUID;
+
+  newUID = [NSMutableString string];
+  now = [NSDate date];
+
+  max = [_uid length];
+  done = 0;
+  count = 0;
+  while (done < 8 && count < max)
+    {
+      currentChar = [_uid characterAtIndex: count];
+      if ([asciiAlphaNumericCS characterIsMember: currentChar])
+       {
+         [newUID appendFormat: @"%c", currentChar];
+         done++;
+       }
+      count++;
+    }
+
+  return [NSString stringWithFormat: @"%@%u",
+                  newUID, [now timeIntervalSince1970]];
+}
+
+- (NSException *)createFolderOfType:(NSString *)_type
+ withName:(NSString*)_name atPath:(NSString *)_path
+{
+  // TBD: badly broken, needs to be wrapped in a transaction.
+  // TBD: would be best to perform all operations as a single SQL statement.
+  GCSFolderType    *ftype;
+  NSString         *tableName, *quickTableName, *aclTableName;
+  NSString         *baseURL, *pathElement;
+  EOAdaptorChannel <GCSEOAdaptorChannel> *channel;
+  NSEnumerator     *pathElements;
+  NSMutableArray   *paths;
+  NSException      *error;
+  NSString         *sql;
+
+  paths = [[NSMutableArray alloc] initWithCapacity: 5];
+
+  pathElements = [[_path componentsSeparatedByString: @"/"] objectEnumerator];
+  while ((pathElement = [pathElements nextObject]) != nil) {
+    NSString *p = [[NSString alloc] initWithFormat: @"'%@'", pathElement];
+    [paths addObject: p];
+    [p release]; p = nil;
+  }
+  while ([paths count] < 5)
+    [paths addObject: @"NULL"];
+
+  // TBD: fix SQL injection issue!
+  sql = [NSString stringWithFormat: @"SELECT * FROM %@ WHERE c_path = '%@'",
+                   [self folderInfoTableName], _path];
+  if ([[self performSQL: sql] isNotEmpty]) {
+    return [NSException exceptionWithName:@"GCSExitingFolder"
+                       reason:@"a folder already exists at that path"
+                       userInfo:nil];
+  }
+  if ((ftype = [self folderTypeWithName:_type]) == nil) {
+    return [NSException exceptionWithName:@"GCSMissingFolderType"
+                       reason:@"missing folder type"userInfo:nil];
+  }
+  if ((channel = [self acquireOpenChannel]) == nil) {
+    return [NSException exceptionWithName:@"GCSNoChannel"
+                       reason:@"could not open channel"
+                       userInfo:nil];
+  }
+
+  tableName = [self baseTableNameWithUID: [paths objectAtIndex: 2]];
+  quickTableName = [tableName stringByAppendingString: @"_quick"];
+  aclTableName   = [tableName stringByAppendingString: @"_acl"];
+
+  sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
+  if ((error = [channel evaluateExpressionX:sql]) != nil)
+    ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+  
+  sql = [@"DROP TABLE " stringByAppendingString:tableName];
+  if ((error = [channel evaluateExpressionX:sql]) != nil)
+    ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+
+  sql = [@"DROP TABLE " stringByAppendingString:aclTableName];
+  if ((error = [channel evaluateExpressionX:sql]) != nil)
+    ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+  
+  if ((error = [channel createGCSFolderTableWithName: tableName]) != nil)
+    return error;
+  
+  sql = [ftype sqlQuickCreateWithTableName: quickTableName];
+  if (debugSQLGen) [self logWithFormat:@"quick-Create: %@", sql];
+
+  if ((error = [channel evaluateExpressionX:sql]) != nil) {
+    /* 'rollback' TBD: wrap in proper tx */
+    sql = [@"DROP TABLE " stringByAppendingString:tableName];
+    if ((error = [channel evaluateExpressionX:sql]) != nil) {
+      [self warnWithFormat:@"failed to drop freshly created table: %@", 
+             tableName];
+    }
+    
+    return error;
+  }
+
+  if (debugSQLGen) [self logWithFormat:@"acl-Create: %@", sql];
+  if ((error = [channel createGCSFolderACLTableWithName: aclTableName])
+      != nil) {
+    /* 'rollback' TBD: wrap in proper tx */
+    sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
+    if ((error = [channel evaluateExpressionX:sql]) != nil) {
+      [self warnWithFormat:@"failed to drop freshly created table: %@", 
+             tableName];
+    }
+    sql = [@"DROP TABLE " stringByAppendingString:tableName];
+    if ((error = [channel evaluateExpressionX:sql]) != nil) {
+      [self warnWithFormat:@"failed to drop freshly created table: %@", 
+             tableName];
+    }
+    
+    return error;
+  }
+  
+  // TBD: fix SQL injection issues
+  baseURL
+    = [[folderInfoLocation absoluteString] stringByDeletingLastPathComponent];
+
+  sql = [NSString stringWithFormat: @"INSERT INTO %@"
+                       @"        (c_path, c_path1, c_path2, c_path3, c_path4,"
+                       @"         c_foldername, c_location, c_quick_location,"
+                       @"         c_acl_location, c_folder_type)"
+                       @" VALUES ('%@', %@, %@, %@, %@, '%@', '%@/%@',"
+                       @"         '%@/%@', '%@/%@', '%@')",
+                       [self folderInfoTableName], _path,
+                       [paths objectAtIndex: 1], [paths objectAtIndex: 2],
+                       [paths objectAtIndex: 3], [paths objectAtIndex: 4],
+                       _name,
+                      baseURL, tableName,
+                      baseURL, quickTableName,
+                      baseURL, aclTableName,
+                       _type];
+  if ((error = [channel evaluateExpressionX:sql]) != nil)
+    return error;
+
+  [paths release]; paths = nil;
+  [self releaseChannel: channel];
+
+  return nil;
+}
+  
+- (NSException *)deleteFolderAtPath:(NSString *)_path {
+  GCSFolder    *folder;
+  NSArray      *fnames;
+  NSString     *sql, *ws;
+  EOAdaptorChannel *channel;
+  NSException *ex;
+
+  if ((folder = [self folderAtPath:_path]) == nil) {
+    return [NSException exceptionWithName:@"GCSMissingFolder"
+                       reason:@"missing folder"
+                       userInfo:nil];
+  }
+
+  if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+    [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+    return nil;
+  }
+
+  ws = [self generateSQLWhereForInternalNames:fnames 
+            exactMatch:YES orDirectSubfolderMatch:NO];
+
+  sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@",
+                 [self folderInfoTableName], ws];
+  if ((channel = [self acquireOpenChannel]) == nil) {
+    return [NSException exceptionWithName:@"GCSNoChannel"
+                       reason:@"could not "
+                       userInfo:nil];
+  }
+
+  if ((ex = [channel evaluateExpressionX:sql]) != nil) {
+    [self releaseChannel:channel];
+    return ex;
+  }
+
+  [self releaseChannel:channel];
+
+  return [folder deleteFolder];
+}
+
+/* folder types */
+
+- (GCSFolderType *)folderTypeWithName:(NSString *)_name {
+  NSString *specificName;
+  GCSFolderType *type;
+
+  if ([_name length] == 0)
+    _name = GCSGenericFolderTypeName;
+
+  specificName = [NSString stringWithFormat: @"%@-%@",
+                          _name, [folderInfoLocation scheme]];
+  type = [self->nameToType objectForKey: [specificName lowercaseString]];
+  if (!type)
+    type = [self->nameToType objectForKey:[_name lowercaseString]];
+
+  return type;
+}
+
+/* cache management */
+
+- (void)reset {
+  /* does nothing in the moment, but we need a way to signal refreshes */
+}
+
+/* debugging */
+
+- (BOOL)isDebuggingEnabled {
+  return debugOn;
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+  
+  [ms appendFormat:@" url=%@", [self->folderInfoLocation absoluteString]];
+  [ms appendFormat:@" channel-manager=0x%p", [self channelManager]];
+  
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSFolderManager */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.h
new file mode 100644 (file)
index 0000000..8c62e05
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_GCSFolderType_H__
+#define __GDLContentStore_GCSFolderType_H__
+
+/*
+  GCSFolderType
+
+  This is the "model" of a folder, it specifies what quick attributes are
+  available, in which tables it is stored and how it is retrieved.
+  
+  For now, we only support one 'quick' table (we might want to have multiple
+  ones in the future).
+  
+  Note: using the 'folderQualifier' we are actually prepared for 'multiple
+        folders in a single table' setups. So in case we want to go that
+        route later, we can still do it :-)
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSString, NSArray, NSDictionary;
+@class EOQualifier;
+@class NGResourceLocator;
+@class GCSFolder, GCSFieldExtractor;
+
+@interface GCSFolderType : NSObject
+{
+  NSString     *blobTablePattern;  // eg 'SOGo_$folderId$_blob
+  NSString     *quickTablePattern; // eg 'SOGo_$folderId$_quick
+  NSArray      *fields;            // GCSFieldInfo objects
+  NSDictionary *fieldDict;         // maps a name to GCSFieldInfo
+  EOQualifier  *folderQualifier;   // to further limit the table set
+  NSString     *extractorClassName;
+  GCSFieldExtractor *extractor;
+}
+
++ (id)folderTypeWithName:(NSString *)_type;
+
+- (id)initWithPropertyList:(id)_plist;
+- (id)initWithContentsOfFile:(NSString *)_path;
+- (id)initWithFolderTypeName:(NSString *)_typeName;
+
+/* operations */
+
+- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder;
+- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder;
+
+/* generating SQL */
+
+- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName;
+
+/* quick support */
+
+- (GCSFieldExtractor *)quickExtractor;
+
+/* locator used to find .ocs files */
+
++ (NGResourceLocator *)resourceLocator;
+
+@end
+
+#endif /* __GDLContentStore_GCSFolderType_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m b/SOPE/sope-gdl1/GDLContentStore/GCSFolderType.m
new file mode 100644 (file)
index 0000000..76f2d36
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSFolderType.h"
+#include "GCSFolder.h"
+#include "GCSFieldInfo.h"
+#include "GCSFieldExtractor.h"
+#include "common.h"
+#include <EOControl/EOKeyValueCoding.h>
+#include <NGExtensions/NGResourceLocator.h>
+
+@implementation GCSFolderType
+
+- (id)initWithPropertyList:(id)_plist {
+  if ((self = [super init])) {
+    NSDictionary *plist = _plist;
+    
+    self->blobTablePattern  = [[plist objectForKey:@"blobTablePattern"] copy];
+    self->quickTablePattern = [[plist objectForKey:@"quickTablePattern"]copy];
+    
+    self->extractorClassName = 
+      [[plist objectForKey:@"extractorClassName"] copy];
+    // TODO: qualifier;
+    
+    self->fields = [[GCSFieldInfo fieldsForPropertyList:
+                                   [plist objectForKey:@"fields"]] retain];
+  }
+  return self;
+}
+
+- (id)initWithContentsOfFile:(NSString *)_path {
+  NSDictionary *plist;
+  
+  plist = [NSDictionary dictionaryWithContentsOfFile:_path];
+  if (plist == nil) {
+    NSLog(@"ERROR(%s): could not read dictionary at path %@", 
+         __PRETTY_FUNCTION__, _path);
+    [self release];
+    return nil;
+  }
+  return [self initWithPropertyList:plist];
+}
+
++ (NGResourceLocator *)resourceLocator {
+  NGResourceLocator *loc;
+  
+  // TODO: fix me, GCS instead of OCS
+  loc = [NGResourceLocator resourceLocatorForGNUstepPath:
+                            @"Library/OCSTypeModels"
+                           fhsPath:@"share/ocs"];
+  return loc;
+}
+
++ (id)folderTypeWithName:(NSString *)_typeName {
+  NSString *filename, *path;
+
+  // TODO: fix me, GCS instead of OCS
+  filename = [_typeName stringByAppendingPathExtension:@"ocs"];
+  path     = [[self resourceLocator] lookupFileWithName:filename];
+  
+  if (path != nil)
+    return [[self alloc] initWithContentsOfFile:path];
+
+  NSLog(@"ERROR(%s): did not find model for type: '%@'", 
+       __PRETTY_FUNCTION__, _typeName);
+  return nil;
+}
+
+- (id)initWithFolderTypeName:(NSString *)_typeName {
+  // DEPRECATED
+  [self release];
+  return [[GCSFolderType folderTypeWithName:_typeName] retain];
+}
+
+- (void)dealloc {
+  [self->extractor          release];
+  [self->extractorClassName release];
+  [self->blobTablePattern   release];
+  [self->quickTablePattern  release];
+  [self->fields             release];
+  [self->fieldDict          release];
+  [self->folderQualifier    release];
+  [super dealloc];
+}
+
+/* operations */
+
+- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder {
+  return [self->blobTablePattern 
+             stringByReplacingVariablesWithBindings:_folder];
+}
+- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder {
+  return [self->quickTablePattern
+             stringByReplacingVariablesWithBindings:_folder];
+}
+
+- (EOQualifier *)qualifierForFolder:(GCSFolder *)_folder {
+  NSArray      *keys;
+  NSDictionary *bindings;
+  
+  keys = [[self->folderQualifier allQualifierKeys] allObjects];
+  if ([keys count] == 0)
+    return self->folderQualifier;
+
+  bindings = [_folder valuesForKeys:keys];
+  return [self->folderQualifier qualifierWithBindings:bindings
+                               requiresAllVariables:NO];
+}
+
+/* generating SQL */
+
+- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName {
+  NSMutableString *sql;
+  unsigned i, count;
+  
+  sql = [NSMutableString stringWithCapacity:512];
+  [sql appendString:@"CREATE TABLE "];
+  [sql appendString:_tabName];
+  [sql appendString:@" (\n"];
+
+  count = [self->fields count];
+  for (i = 0; i < count; i++) {
+    if (i > 0) [sql appendString:@",\n"];
+    
+    [sql appendString:@"  "];
+    [sql appendString:[[self->fields objectAtIndex:i] sqlCreateSection]];
+  }
+  
+  [sql appendString:@"\n)"];
+  
+  return sql;
+}
+
+/* quick support */
+
+- (GCSFieldExtractor *)quickExtractor {
+  Class clazz;
+  
+  if (self->extractor != nil) {
+    return [self->extractor isNotNull]
+      ? self->extractor : (GCSFieldExtractor *)nil;
+  }
+
+  clazz = self->extractorClassName
+    ? NSClassFromString(self->extractorClassName)
+    : [GCSFieldExtractor class];
+  if (clazz == Nil) {
+    [self logWithFormat:@"ERROR: did not find field extractor class!"];
+    return nil;
+  }
+  
+  if ((self->extractor = [[clazz alloc] init]) == nil) {
+    [self logWithFormat:@"ERROR: could not create field extractor of class %@",
+           clazz];
+    return nil;
+  }
+  return self->extractor;
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:256];
+  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
+
+  [ms appendFormat:@" blobtable='%@'",  self->blobTablePattern];
+  [ms appendFormat:@" quicktable='%@'", self->quickTablePattern];
+  [ms appendFormat:@" fields=%@",       self->fields];
+  [ms appendFormat:@" extractor=%@",    self->extractorClassName];
+  
+  if (self->folderQualifier)
+    [ms appendFormat:@" qualifier=%@", self->folderQualifier];
+  
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* GCSFolderType */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.h
new file mode 100644 (file)
index 0000000..cdd57c2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef        __GDLContentStore_GCSStringFormatter_H__
+#define        __GDLContentStore_GCSStringFormatter_H__
+
+#include <NGExtensions/NSString+Escaping.h>
+
+@interface GCSStringFormatter : NSObject < NGStringEscaping >
+{
+}
+
++ (id)sharedFormatter;
+
+- (NSString *)stringByFormattingString:(NSString *)_s;
+
+@end
+
+#endif /* __GDLContentStore_GCSStringFormatter_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m b/SOPE/sope-gdl1/GDLContentStore/GCSStringFormatter.m
new file mode 100644 (file)
index 0000000..b08bab5
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "GCSStringFormatter.h"
+#include "common.h"
+
+@implementation GCSStringFormatter
+
+static NSCharacterSet *escapeSet = nil;
+
++ (void)initialize {
+  static BOOL didInit = NO;
+
+  if(didInit)
+    return;
+
+  didInit = YES;
+  escapeSet = 
+    [[NSCharacterSet characterSetWithCharactersInString:@"\\'"] retain];
+}
+
++ (id)sharedFormatter {
+  static id sharedInstance = nil;
+  if(!sharedInstance) {
+    sharedInstance = [[self alloc] init];
+  }
+  return sharedInstance;
+}
+
+- (NSString *)stringByFormattingString:(NSString *)_s {
+  NSString *s;
+
+  s = [_s stringByEscapingCharactersFromSet:escapeSet
+          usingStringEscaping:self];
+  return [NSString stringWithFormat:@"'%@'", s];
+}
+
+- (NSString *)stringByEscapingString:(NSString *)_s {
+  if([_s isEqualToString:@"\\"]) {
+    return @"\\\\"; /* easy ;-) */
+  }
+  return @"\\'";
+}
+
+@end /* GCSStringFormatter */
diff --git a/SOPE/sope-gdl1/GDLContentStore/GNUmakefile b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile
new file mode 100644 (file)
index 0000000..85b394e
--- /dev/null
@@ -0,0 +1,77 @@
+# GNUstep makefiles
+
+include ../../../config.make
+include $(GNUSTEP_MAKEFILES)/common.make
+include ./Version
+
+GNUSTEP_INSTALLATION_DIR = ${GNUSTEP_LOCAL_ROOT}
+
+ifneq ($(frameworks),yes)
+LIBRARY_NAME = libGDLContentStore
+else
+FRAMEWORK_NAME = GDLContentStore
+endif
+
+libGDLContentStore_PCH_FILE = common.h
+libGDLContentStore_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION)
+libGDLContentStore_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION)
+
+TOOL_NAME = gcs_ls gcs_mkdir gcs_cat gcs_recreatequick gcs_gensql
+
+libGDLContentStore_HEADER_FILES_DIR         = .
+libGDLContentStore_HEADER_FILES_INSTALL_DIR = /GDLContentStore
+FHS_HEADER_FILES_INSTALL_DIR = $(libGDLContentStore_HEADER_FILES_INSTALL_DIR)
+
+libGDLContentStore_HEADER_FILES +=             \
+       NSURL+GCS.h                             \
+       EOAdaptorChannel+GCS.h                  \
+       \
+       GCSContext.h                            \
+       GCSFieldInfo.h                          \
+       GCSFolder.h                             \
+       GCSFolderManager.h                      \
+       GCSFolderType.h                         \
+       GCSChannelManager.h                     \
+       GCSFieldExtractor.h                     \
+       GCSStringFormatter.h                    \
+
+libGDLContentStore_OBJC_FILES +=               \
+       NSURL+GCS.m                             \
+       EOAdaptorChannel+GCS.m                  \
+       EOQualifier+GCS.m                       \
+       \
+       GCSContext.m                            \
+       GCSFieldInfo.m                          \
+       GCSFolder.m                             \
+       GCSFolderManager.m                      \
+       GCSFolderType.m                         \
+       GCSChannelManager.m                     \
+       GCSFieldExtractor.m                     \
+       GCSStringFormatter.m                    \
+
+gcs_ls_OBJC_FILES            += gcs_ls.m
+gcs_mkdir_OBJC_FILES         += gcs_mkdir.m
+gcs_cat_OBJC_FILES           += gcs_cat.m
+gcs_gensql_OBJC_FILES        += gcs_gensql.m
+gcs_recreatequick_OBJC_FILES += gcs_recreatequick.m
+
+
+# framework support
+
+GDLContentStore_PCH_FILE     = $(libGDLContentStore_PCH_FILE)
+GDLContentStore_HEADER_FILES = $(libGDLContentStore_HEADER_FILES)
+GDLContentStore_OBJC_FILES   = $(libGDLContentStore_OBJC_FILES)
+GDLContentStore_SUBPROJECTS  = $(libGDLContentStore_SUBPROJECTS)
+
+
+# building
+
+-include GNUmakefile.preamble
+ifneq ($(frameworks),yes)
+include $(GNUSTEP_MAKEFILES)/library.make
+else
+include $(GNUSTEP_MAKEFILES)/framework.make
+endif
+include $(GNUSTEP_MAKEFILES)/tool.make
+-include GNUmakefile.postamble
+include fhs.make
diff --git a/SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble b/SOPE/sope-gdl1/GDLContentStore/GNUmakefile.preamble
new file mode 100644 (file)
index 0000000..ada5aa1
--- /dev/null
@@ -0,0 +1,78 @@
+# compilation settings
+
+SOPE_ROOT=../..
+
+ADDITIONAL_CPPFLAGS += -Wall
+
+ADDITIONAL_INCLUDE_DIRS += -I. -I..
+
+
+# dependencies
+
+libGDLContentStore_LIBRARIES_DEPEND_UPON += \
+       -lGDLAccess                     \
+       -lNGExtensions -lEOControl      \
+       -lDOM -lSaxObjC
+
+GDLContentStore_LIBRARIES_DEPEND_UPON += \
+       -framework GDLAccess    \
+       -framework NGExtensions -framework EOControl \
+       -framework DOM -framework SaxObjC
+
+ifneq ($(frameworks),yes)
+GCS_TOOL_LIBS += \
+       -lGDLContentStore -lGDLAccess   \
+       -lNGExtensions -lEOControl      \
+       -lDOM -lSaxObjC
+else
+GCS_TOOL_LIBS += \
+       -framework GDLContentStore -framework GDLAccess \
+       -framework NGExtensions -framework EOControl    \
+       -framework DOM -framework SaxObjC
+endif
+
+gcs_ls_TOOL_LIBS            += $(GCS_TOOL_LIBS)
+gcs_mkdir_TOOL_LIBS         += $(GCS_TOOL_LIBS)
+gcs_cat_TOOL_LIBS           += $(GCS_TOOL_LIBS)
+gcs_recreatequick_TOOL_LIBS += $(GCS_TOOL_LIBS)
+gcs_gensql_TOOL_LIBS        += $(GCS_TOOL_LIBS)
+
+gcs_ls_PCH_FILE            += common.h
+gcs_mkdir_PCH_FILE         += common.h
+gcs_cat_PCH_FILE           += common.h
+gcs_recreatequick_PCH_FILE += common.h
+gcs_gensql_PCH_FILE        += common.h
+
+
+# library/framework search pathes
+
+DEP_DIRS = \
+       .               \
+       ../GDLAccess    \
+       $(SOPE_ROOT)/sope-core/NGExtensions     \
+       $(SOPE_ROOT)/sope-core/EOControl        \
+       $(SOPE_ROOT)/sope-xml/DOM               \
+       $(SOPE_ROOT)/sope-xml/SaxObjC
+
+ifneq ($(frameworks),yes)
+ADDITIONAL_LIB_DIRS += \
+       $(foreach dir,$(DEP_DIRS),\
+         -L$(GNUSTEP_BUILD_DIR)/$(dir)/$(GNUSTEP_OBJ_DIR_NAME))
+else
+ADDITIONAL_LIB_DIRS += \
+       $(foreach dir,$(DEP_DIRS),-F$(GNUSTEP_BUILD_DIR)/$(dir))
+endif
+
+SYSTEM_LIB_DIR += $(CONFIGURE_SYSTEM_LIB_DIR)
+
+
+# platform specific settings
+
+ifeq ($(FOUNDATION_LIB),apple)
+libGDLContentStore_PREBIND_ADDR="0xC7700000"
+libGDLContentStore_LDFLAGS += -seg1addr $(libGDLContentStore_PREBIND_ADDR)
+endif
+
+ifeq ($(findstring openbsd3, $(GNUSTEP_HOST_OS)), openbsd3)
+GCS_TOOL_LIBS += -liconv
+endif
diff --git a/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.h
new file mode 100644 (file)
index 0000000..c03ea40
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __GDLContentStore_NSURL_GCS_H__
+#define __GDLContentStore_NSURL_GCS_H__
+
+#import <Foundation/NSURL.h>
+
+/*
+  "Database URLs"
+  
+  We use the schema:
+    postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename]
+*/
+
+@interface NSURL(GCS)
+
+- (NSString *)gcsDatabaseName;
+- (NSString *)gcsTableName;
+
+@end
+
+#endif /* __GDLContentStore_NSURL_GCS_H__ */
diff --git a/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m b/SOPE/sope-gdl1/GDLContentStore/NSURL+GCS.m
new file mode 100644 (file)
index 0000000..285aad6
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "NSURL+GCS.h"
+#include "common.h"
+
+@implementation NSURL(GCS)
+
+- (NSString *)gcsPathComponent:(unsigned)_idx {
+  NSString *p;
+  NSArray  *pcs;
+  unsigned len;
+  
+  p = [self path];
+  if ([p length] == 0)
+    return nil;
+  
+  pcs = [p componentsSeparatedByString:@"/"];
+  if ((len = [pcs count]) == 0)
+    return nil;
+  if (len <= _idx)
+    return  nil;
+  return [pcs objectAtIndex:_idx];
+}
+
+- (NSString *)gcsDatabaseName {
+  return [self gcsPathComponent:1];
+}
+- (NSString *)gcsTableName {
+  return [[self path] lastPathComponent];
+}
+
+@end /* NSURL(GCS) */
diff --git a/SOPE/sope-gdl1/GDLContentStore/README b/SOPE/sope-gdl1/GDLContentStore/README
new file mode 100644 (file)
index 0000000..981e3d9
--- /dev/null
@@ -0,0 +1,117 @@
+Storage Backend
+===============
+
+The storage backend implements the "low level" folder abstraction, which is
+basically an arbitary "BLOB" containing some document. The feature is that
+we extract "quick access" / "searchable" attributes from the document content.
+
+Further it contains the "folder management" API, as named folders can be stored
+in different databases.
+Note: we need a way to tell where "new" folders should be created
+Note: to sync with LDAP we need to periodically delete or archive old folders
+
+Folders have associated a type (like 'calendar') which defines the query
+attributes and serialization format.
+
+TODO
+====
+- fix some OCS naming
+  - defaults
+  - lookup directories
+- hierarchies deeper than 4 (properly filter on path in OCS)
+
+Open Questions
+==============
+
+System-meta-data in the blob-table or in the quick-table?
+- master data belongs into the blob table
+- could be regular 'NSxxx' keys to differentiate meta data from
+
+Class Hierarchy
+===============
+
+  [NSObject]
+    OCSContext                  - tracking context
+    OCSFolder                   - represents a single folder
+    OCSFolderManager            - manages folders
+    OCSFolderType               - the mapping info for a specific folder-type
+    OCSFieldInfo                - mapping info for one 'quick field'
+    OCSChannelManager           - maintains EOAdaptorChannel objects
+
+  TBD:
+  - field 'extractor'
+  - field 'value' (eg array values for participants?)
+  - BLOB archiver/unarchiver
+
+Defaults
+========
+
+  OCSFolderInfoURL - the DB URL where the folder-info table is located
+    eg: http://OGo:OGo@localhost/test/folder_info
+
+  OCSFolderManagerDebugEnabled      - enable folder-manager debug logs
+  OCSFolderManagerSQLDebugEnabled   - enable folder-manager SQL gen debug logs
+
+  OCSChannelManagerDebugEnabled     - enable channel debug pooling logs
+  OCSChannelManagerPoolDebugEnabled - debug pool handle allocation
+  
+  OCSChannelExpireAge       - if that age in seconds is exceeded, a channel 
+                              will be removed from the pool
+  OCSChannelCollectionTimer - time in seconds. each n-seconds the pool will be
+                             checked for channels too old
+  
+  [PGDebugEnabled] - enable PostgreSQL adaptor debugging
+
+URLs
+====
+
+  "Database URLs"
+  
+  We use the schema:
+    postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename]
+
+Support Tools
+=============
+
+- tools we need:
+  - one to recreate a quick table based on the blob table
+
+Notes
+=====
+
+- need to use http:// URLs for connect info, until generic URLs in 
+  libFoundation are fixed (the parses breaks on the login/password parts)
+
+QA
+==
+
+Q: Why do we use two tables, we could store the quick columns in the blob?
+==
+They could be in the same table. We considered using separate tables since the 
+quick table is likely to be recreated now and then if BLOB indexing 
+requirements change.
+Actually one could even use different _quick tables which share a common BLOB 
+table.
+(a quick table is nothing more than a database index and like with DB indexes 
+ multiple ones for different requirements can make sense).
+
+Further it might improve caching behaviour for row based caches (the quick 
+table is going to be queried much more often) - not sure whether this is 
+relevant with PostgreSQL, probably not?
+
+Q: Can we use a VARCHAR primary key?
+==
+We asked in the postgres IRC channel and apparently the performance penalty of
+string primary keys isn't big.
+We could also use an 'internal' int sequence in addition (might be useful for
+supporting ZideLook)
+Motivation: the 'iCalendar' ID is a string and usually looks like a GUID.
+
+Q: Why using VARCHAR instead of TEXT in the BLOB?
+==
+To quote PostgreSQL documentation:
+"There are no performance differences between these three types, apart from 
+ the increased storage size when using the blank-padded type."
+So varchar(xx) is just a large TEXT. Since we intend to store mostly small
+snippets of data (tiny XML fragments), we considered VARCHAR the more 
+appropriate type.
diff --git a/SOPE/sope-gdl1/GDLContentStore/Version b/SOPE/sope-gdl1/GDLContentStore/Version
new file mode 100644 (file)
index 0000000..eb33528
--- /dev/null
@@ -0,0 +1,14 @@
+# Version file
+
+MAJOR_VERSION:=4
+MINOR_VERSION:=7
+SUBMINOR_VERSION:=49
+
+# v4.5.29 requires libNGExtensions v4.5.161
+# v4.5.26 does not require libNGiCal anymore
+# v0.9.19 requires libNGiCal       v4.5.40
+# v0.9.18 requires libNGiCal       v4.5.38
+# v0.9.17 requires libNGiCal       v4.5.37
+# v0.9.11 requires libFoundation   v1.0.63
+# v0.9.11 requires libNGExtensions v4.3.125
+# v0.9.7  requires libGDLAccess    v1.1.35
diff --git a/SOPE/sope-gdl1/GDLContentStore/common.h b/SOPE/sope-gdl1/GDLContentStore/common.h
new file mode 100644 (file)
index 0000000..fa68afc
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+  Copyright (C) 2004 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+// $Id: common.h 673 2005-03-20 19:09:53Z helge $
+
+#import <Foundation/Foundation.h>
+#import <Foundation/NSURL.h>
+
+#include <NGExtensions/NGExtensions.h>
+
+#if NeXT_RUNTIME || APPLE_RUNTIME
+#  define objc_free(__mem__)    free(__mem__)
+#  define objc_malloc(__size__) malloc(__size__)
+#  define objc_calloc(__cnt__, __size__) calloc(__cnt__, __size__)
+#  define objc_realloc(__ptr__, __size__) realloc(__ptr__, __size__)
+#  ifndef sel_eq
+#    define sel_eq(sela,selb) (sela==selb?YES:NO)
+#  endif
+#endif
diff --git a/SOPE/sope-gdl1/GDLContentStore/fhs.make b/SOPE/sope-gdl1/GDLContentStore/fhs.make
new file mode 100644 (file)
index 0000000..24d1e43
--- /dev/null
@@ -0,0 +1,44 @@
+# postprocessing
+
+# FHS support (this is a hack and is going to be done by gstep-make!)
+
+# NOTE: you need to define FHS_HEADER_FILES_INSTALL_DIR for one library
+
+ifneq ($(FHS_INSTALL_ROOT),)
+
+FHS_INCLUDE_DIR=$(FHS_INSTALL_ROOT)/include/
+FHS_BIN_DIR=$(FHS_INSTALL_ROOT)/bin
+FHS_LIB_DIR=$(CONFIGURE_FHS_INSTALL_LIBDIR)
+
+NONFHS_LIBDIR="$(GNUSTEP_LIBRARIES)/$(GNUSTEP_TARGET_LDIR)/"
+NONFHS_LIBNAME="$(LIBRARY_NAME)$(LIBRARY_NAME_SUFFIX)$(SHARED_LIBEXT)"
+NONFHS_BINDIR="$(GNUSTEP_TOOLS)/$(GNUSTEP_TARGET_LDIR)"
+
+
+fhs-header-dirs ::
+       $(MKDIRS) $(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR)
+
+fhs-bin-dirs ::
+       $(MKDIRS) $(FHS_BIN_DIR)
+
+
+move-headers-to-fhs :: fhs-header-dirs
+       @echo "moving headers to $(FHS_INCLUDE_DIR) .."
+       mv $(GNUSTEP_HEADERS)$(FHS_HEADER_FILES_INSTALL_DIR)/*.h \
+         $(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR)/
+
+move-libs-to-fhs :: 
+       @echo "moving libs to $(FHS_LIB_DIR) .."
+       mv $(NONFHS_LIBDIR)/$(NONFHS_LIBNAME)* $(FHS_LIB_DIR)/
+
+move-tools-to-fhs :: fhs-bin-dirs
+       @echo "moving tools from $(NONFHS_BINDIR) to $(FHS_BIN_DIR) .."
+       for i in $(TOOL_NAME); do \
+         mv "$(NONFHS_BINDIR)/$${i}" $(FHS_BIN_DIR); \
+       done
+
+move-to-fhs :: move-headers-to-fhs move-libs-to-fhs move-tools-to-fhs
+
+after-install :: move-to-fhs
+
+endif
diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_cat.m b/SOPE/sope-gdl1/GDLContentStore/gcs_cat.m
new file mode 100644 (file)
index 0000000..7cf768c
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSUserDefaults, NSArray;
+@class GCSFolderManager;
+
+@interface Tool : NSObject
+{
+  NSUserDefaults   *ud;
+  GCSFolderManager *folderManager;
+}
+
++ (int)runWithArgs:(NSArray *)_args;
+- (int)run;
+
+@end
+
+#include <GDLContentStore/GCSFolder.h>
+#include <GDLContentStore/GCSFolderManager.h>
+#include "common.h"
+
+@implementation Tool
+
+- (id)init {
+  if ((self = [super init])) {
+    self->ud            = [[NSUserDefaults standardUserDefaults]   retain];
+    self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
+  }
+  return self;
+}
+- (void)dealloc {
+  [self->ud            release];
+  [self->folderManager release];
+  [super dealloc];
+}
+
+/* operation */
+
+- (int)runOnPath:(NSString *)_path {
+  GCSFolder   *folder;
+  NSString    *dirname, *filename;
+  NSString    *content;
+  
+  dirname  = [_path stringByDeletingLastPathComponent];
+  filename = [_path lastPathComponent];
+  
+  if ((folder = [self->folderManager folderAtPath:dirname]) == nil) {
+    [self logWithFormat:@"did not find folder for file: '%@'", dirname];
+    return 1;
+  }
+  
+  if ((content = [folder fetchContentWithName:filename]) == nil) {
+    [self logWithFormat:@"did not find file: '%@'", _path];
+    return 1;
+  }
+  
+  printf("%s\n", [content cString]);
+  
+  return 0;
+}
+
+- (int)run {
+  NSEnumerator *e;
+  NSString *path;
+  
+  [self logWithFormat:@"manager: %@", self->folderManager];
+  
+  if (![self->folderManager canConnect]) {
+    [self logWithFormat:@"cannot connect folder-info database!"];
+    return 1;
+  }
+  
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  while ((path = [e nextObject]))
+    [self runOnPath:path];
+  
+  return 0;
+}
++ (int)runWithArgs:(NSArray *)_args {
+  return [(Tool *)[[[self alloc] init] autorelease] run];
+}
+
+@end /* Tool */
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int rc;
+
+  pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+  rc = [Tool runWithArgs:
+               [[NSProcessInfo processInfo] argumentsWithoutDefaults]];
+  
+  [pool release];
+  return rc;
+}
diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m b/SOPE/sope-gdl1/GDLContentStore/gcs_gensql.m
new file mode 100644 (file)
index 0000000..13e0cae
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+  Copyright (C) 2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSUserDefaults, NSArray;
+@class GCSFolderManager;
+
+@interface Tool : NSObject
+{
+  NSUserDefaults *ud;
+}
+
++ (int)runWithArgs:(NSArray *)_args;
+- (int)run;
+
+@end
+
+#include <GDLContentStore/GCSFolderType.h>
+#include "common.h"
+
+@implementation Tool
+
+- (id)init {
+  if ((self = [super init])) {
+    self->ud = [[NSUserDefaults standardUserDefaults] retain];
+  }
+  return self;
+}
+- (void)dealloc {
+  [self->ud release];
+  [super dealloc];
+}
+
+/* operation */
+
+- (int)runOnTable:(NSString *)_tableName typeName:(NSString *)_typeName {
+  GCSFolderType *folderType;
+  
+  if ((folderType = [GCSFolderType folderTypeWithName:_typeName]) != nil) {
+    NSString *s;
+    
+    s = [folderType sqlQuickCreateWithTableName:_tableName];
+    
+    fwrite([s cString], 1, [s cStringLength], stdout);
+    printf("\n");
+  }
+  else {
+    fprintf(stderr, "ERROR: did not find GCS type: '%s'\n", 
+           [_typeName cString]);
+  }
+  
+  return 0;
+}
+
+- (int)run {
+  NSEnumerator *e;
+  NSString *tableName, *typeName;
+  
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  while ((tableName = [e nextObject]) != nil) {
+    typeName = [e nextObject];
+    if (typeName == nil) {
+      [self logWithFormat:@"got tablename '%@' but no type?!", tableName];
+      break;
+    }
+    
+    [self runOnTable:tableName typeName:typeName];
+  }
+  
+  return 0;
+}
++ (int)runWithArgs:(NSArray *)_args {
+  return [(Tool *)[[[self alloc] init] autorelease] run];
+}
+
+@end /* Tool */
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int rc;
+
+  pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+  rc = [Tool runWithArgs:
+               [[NSProcessInfo processInfo] argumentsWithoutDefaults]];
+  
+  [pool release];
+  return rc;
+}
diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_ls.m b/SOPE/sope-gdl1/GDLContentStore/gcs_ls.m
new file mode 100644 (file)
index 0000000..20de58e
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSUserDefaults, NSArray;
+@class GCSFolderManager;
+
+@interface Tool : NSObject
+{
+  NSUserDefaults   *ud;
+  GCSFolderManager *folderManager;
+}
+
++ (int)runWithArgs:(NSArray *)_args;
+- (int)run;
+
+@end
+
+#include <GDLContentStore/GCSFolder.h>
+#include <GDLContentStore/GCSFolderManager.h>
+#include "common.h"
+
+@implementation Tool
+
+- (id)init {
+  if ((self = [super init])) {
+    self->ud            = [[NSUserDefaults standardUserDefaults]   retain];
+    self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
+  }
+  return self;
+}
+- (void)dealloc {
+  [self->ud            release];
+  [self->folderManager release];
+  [super dealloc];
+}
+
+/* operation */
+
+- (int)runOnPath:(NSString *)_path {
+  NSArray   *subfolders;
+  unsigned  i, count;
+  GCSFolder *folder;
+  
+  [self logWithFormat:@"ls path: '%@'", _path];
+  
+#if 0 // we do not necessarily need the whole hierarchy
+  if (![self->folderManager folderExistsAtPath:_path])
+    [self logWithFormat:@"folder does not exist: '%@'", _path];
+#endif
+  
+  subfolders = [self->folderManager
+                   listSubFoldersAtPath:_path
+                   recursive:[ud boolForKey:@"r"]];
+  if (subfolders == nil) {
+    [self logWithFormat:@"cannot list folder: '%@'", _path];
+    return 1;
+  }
+  
+  for (i = 0, count = [subfolders count]; i < count; i++) {
+    printf("%s\n", [[subfolders objectAtIndex:i] cString]);
+  }
+
+  folder = [self->folderManager folderAtPath:_path];
+  
+  if ([folder isNotNull]) {
+    NSLog(@"folder: %@", folder);
+    
+    NSLog(@"  can%s connect store: %@", [folder canConnectStore] ? "" : "not",
+         [[folder location] absoluteString]);
+    NSLog(@"  can%s connect quick: %@", [folder canConnectQuick] ? "" : "not",
+         [[folder quickLocation] absoluteString]);
+  }
+  else {
+    NSLog(@"ERROR: could not create folder object for path: '%@'", _path);
+  }
+  
+  return 0;
+}
+
+- (int)run {
+  NSEnumerator *e;
+  NSString *path;
+  
+  [self logWithFormat:@"manager: %@", self->folderManager];
+
+  if (![self->folderManager canConnect]) {
+    [self logWithFormat:@"cannot connect folder-info database!"];
+    return 1;
+  }
+  
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  while ((path = [e nextObject]) != nil)
+    [self runOnPath:path];
+  
+  return 0;
+}
++ (int)runWithArgs:(NSArray *)_args {
+  return [(Tool *)[[[self alloc] init] autorelease] run];
+}
+
+@end /* Tool */
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int rc;
+
+  pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+  rc = [Tool runWithArgs:
+               [[NSProcessInfo processInfo] argumentsWithoutDefaults]];
+  
+  [pool release];
+  return rc;
+}
diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m b/SOPE/sope-gdl1/GDLContentStore/gcs_mkdir.m
new file mode 100644 (file)
index 0000000..fc3b354
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSUserDefaults, NSArray;
+@class GCSFolderManager;
+
+@interface Tool : NSObject
+{
+  NSUserDefaults   *ud;
+  GCSFolderManager *folderManager;
+}
+
++ (int)runWithArgs:(NSArray *)_args;
+- (int)run;
+
+@end
+
+#include <GDLContentStore/GCSFolder.h>
+#include <GDLContentStore/GCSFolderManager.h>
+#include "common.h"
+
+@implementation Tool
+
+- (id)init {
+  if ((self = [super init])) {
+    self->ud            = [[NSUserDefaults standardUserDefaults]   retain];
+    self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
+  }
+  return self;
+}
+- (void)dealloc {
+  [self->ud            release];
+  [self->folderManager release];
+  [super dealloc];
+}
+
+/* operation */
+
+- (int)runOnPath:(NSString *)_path type:(NSString *)_type {
+  NSException *error;
+  
+  [self logWithFormat:@"mkdir %@ at path: '%@'", _type, _path];
+  
+  if ([self->folderManager folderExistsAtPath:_path]) {
+    [self logWithFormat:@"folder already exist at path: '%@'", _path];
+    return 1;
+  }
+  
+  if ((error = [self->folderManager createFolderOfType:_type atPath:_path])) {
+    [self logWithFormat:@"creation of folder %@ at %@ failed: %@",
+           _type, _path, error];
+    return 1;
+  }
+  
+  if ([self->folderManager folderExistsAtPath:_path])
+    [self logWithFormat:@"CREATED."];
+  else
+    [self logWithFormat:@"cannot find fresh folder?"];
+  
+  return 0;
+}
+
+- (int)run {
+  NSEnumerator *e;
+  NSString *type;
+  NSString *path;
+  
+  [self logWithFormat:@"manager: %@", self->folderManager];
+  
+  if (![self->folderManager canConnect]) {
+    [self logWithFormat:@"cannot connect folder-info database!"];
+    return 1;
+  }
+  
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  type = [[[e nextObject] copy] autorelease];
+  
+  while ((path = [e nextObject]))
+    [self runOnPath:path type:type];
+  
+  return 0;
+}
++ (int)runWithArgs:(NSArray *)_args {
+  return [(Tool *)[[[self alloc] init] autorelease] run];
+}
+
+@end /* Tool */
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int rc;
+
+  pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+  rc = [Tool runWithArgs:
+               [[NSProcessInfo processInfo] argumentsWithoutDefaults]];
+  
+  [pool release];
+  return rc;
+}
diff --git a/SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m b/SOPE/sope-gdl1/GDLContentStore/gcs_recreatequick.m
new file mode 100644 (file)
index 0000000..d683553
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSObject.h>
+
+@class NSUserDefaults, NSArray;
+@class GCSFolderManager;
+
+@interface Tool : NSObject
+{
+  NSUserDefaults   *ud;
+  GCSFolderManager *folderManager;
+}
+
++ (int)runWithArgs:(NSArray *)_args;
+- (int)run;
+
+@end
+
+#include <GDLContentStore/GCSFolder.h>
+#include <GDLContentStore/GCSFolderManager.h>
+#include "common.h"
+
+@implementation Tool
+
+- (id)init {
+  if ((self = [super init])) {
+    self->ud            = [[NSUserDefaults standardUserDefaults]   retain];
+    self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
+  }
+  return self;
+}
+- (void)dealloc {
+  [self->ud            release];
+  [self->folderManager release];
+  [super dealloc];
+}
+
+/* operation */
+
+- (int)runOnPath:(NSString *)_path {
+  GCSFolder *folder;
+  
+  [self logWithFormat:@"update quick from store at path: '%@'", _path];
+  
+  if (![self->folderManager folderExistsAtPath:_path]) {
+    [self logWithFormat:@"no folder exist at path: '%@'", _path];
+    return 1;
+  }
+  
+  if ((folder = [self->folderManager folderAtPath:_path]) == nil) {
+    [self logWithFormat:@"got no folder object for path: '%@'", _path];
+    return 2;
+  }
+  [self logWithFormat:@"  folder: %@", folder];
+  
+  // TODO:
+  [self logWithFormat:@"  should recreate folder .."];
+  
+  return 0;
+}
+
+- (int)run {
+  NSEnumerator *e;
+  NSString *path;
+  
+  [self logWithFormat:@"manager: %@", self->folderManager];
+  
+  if (![self->folderManager canConnect]) {
+    [self logWithFormat:@"cannot connect folder-info database!"];
+    return 1;
+  }
+  
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  while ((path = [e nextObject]))
+    [self runOnPath:path];
+  
+  return 0;
+}
++ (int)runWithArgs:(NSArray *)_args {
+  return [(Tool *)[[[self alloc] init] autorelease] run];
+}
+
+@end /* Tool */
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int rc;
+
+  pool = [[NSAutoreleasePool alloc] init];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+
+  rc = [Tool runWithArgs:
+               [[NSProcessInfo processInfo] argumentsWithoutDefaults]];
+  
+  [pool release];
+  return rc;
+}