--- /dev/null
+ 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!
--- /dev/null
+Copyright (C) 2004 SKYRIX Software AG
+
+
+Contact: info@skyrix.com
--- /dev/null
+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
+
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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) */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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) */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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 */
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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) */
--- /dev/null
+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.
--- /dev/null
+# 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
--- /dev/null
+/*
+ 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
--- /dev/null
+# 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
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+}