From f62c1fab5d893442aba02eb11835511c615138ce Mon Sep 17 00:00:00 2001 From: tomas Date: Sun, 12 Sep 2004 21:58:48 +0000 Subject: [PATCH] Initial revision --- livesupport/modules/storageServer/Makefile | 153 ++ .../modules/storageServer/etc/doxygen.config | 1144 +++++++++++++ .../modules/storageServer/var/GreenBox.php | 993 +++++++++++ .../modules/storageServer/var/LocStor.php | 124 ++ .../modules/storageServer/var/MetaData.php | 454 +++++ .../storageServer/var/RawMediaData.php | 236 +++ .../modules/storageServer/var/StoredFile.php | 504 ++++++ .../modules/storageServer/var/conf.php | 63 + .../storageServer/var/html/default.css | 19 + .../storageServer/var/html/gbHtmlBrowse.php | 245 +++ .../storageServer/var/html/gbHtmlLogin.php | 73 + .../storageServer/var/html/gbHtmlPerms.php | 111 ++ .../storageServer/var/html/gbHtmlSubj.php | 162 ++ .../storageServer/var/html/gbHtmlTestAuth.php | 9 + .../storageServer/var/html/gbHtmlTestData.php | 13 + .../storageServer/var/html/gbHtml_h.php | 25 + .../modules/storageServer/var/html/gbHttp.php | 197 +++ .../modules/storageServer/var/html/index.php | 34 + .../modules/storageServer/var/index.php | 45 + .../storageServer/var/install/index.php | 34 + .../storageServer/var/install/install.php | 87 + .../storageServer/var/install/uninstall.php | 61 + .../storageServer/var/tests/analyze.php | 25 + .../modules/storageServer/var/tests/ex1.mp3 | Bin 0 -> 48612 bytes .../modules/storageServer/var/tests/ex2.ogg | Bin 0 -> 5354 bytes .../modules/storageServer/var/tests/ex2.wav | Bin 0 -> 19564 bytes .../modules/storageServer/var/tests/index.php | 34 + .../storageServer/var/tests/mdata1.xml | 33 + .../storageServer/var/tests/mdata2.xml | 33 + .../storageServer/var/tests/mdata3.xml | 16 + .../storageServer/var/tests/question.wav | Bin 0 -> 4189 bytes .../storageServer/var/tests/srch_cri1.xml | 14 + .../storageServer/var/tests/testStorage.xml | 12 + .../storageServer/var/xmlrpc/index.php | 34 + .../storageServer/var/xmlrpc/testRunner.sh | 155 ++ .../storageServer/var/xmlrpc/urldecode | 7 + .../storageServer/var/xmlrpc/xmlrpc.inc | 1489 +++++++++++++++++ .../storageServer/var/xmlrpc/xmlrpcs.inc | 450 +++++ .../storageServer/var/xmlrpc/xrLocStor.php | 181 ++ .../storageServer/var/xmlrpc/xr_cli_test.py | 112 ++ 40 files changed, 7381 insertions(+) create mode 100644 livesupport/modules/storageServer/Makefile create mode 100644 livesupport/modules/storageServer/etc/doxygen.config create mode 100644 livesupport/modules/storageServer/var/GreenBox.php create mode 100644 livesupport/modules/storageServer/var/LocStor.php create mode 100644 livesupport/modules/storageServer/var/MetaData.php create mode 100644 livesupport/modules/storageServer/var/RawMediaData.php create mode 100644 livesupport/modules/storageServer/var/StoredFile.php create mode 100644 livesupport/modules/storageServer/var/conf.php create mode 100644 livesupport/modules/storageServer/var/html/default.css create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlBrowse.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlLogin.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlPerms.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlSubj.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlTestAuth.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtmlTestData.php create mode 100644 livesupport/modules/storageServer/var/html/gbHtml_h.php create mode 100644 livesupport/modules/storageServer/var/html/gbHttp.php create mode 100644 livesupport/modules/storageServer/var/html/index.php create mode 100644 livesupport/modules/storageServer/var/index.php create mode 100644 livesupport/modules/storageServer/var/install/index.php create mode 100644 livesupport/modules/storageServer/var/install/install.php create mode 100644 livesupport/modules/storageServer/var/install/uninstall.php create mode 100755 livesupport/modules/storageServer/var/tests/analyze.php create mode 100644 livesupport/modules/storageServer/var/tests/ex1.mp3 create mode 100644 livesupport/modules/storageServer/var/tests/ex2.ogg create mode 100644 livesupport/modules/storageServer/var/tests/ex2.wav create mode 100644 livesupport/modules/storageServer/var/tests/index.php create mode 100644 livesupport/modules/storageServer/var/tests/mdata1.xml create mode 100644 livesupport/modules/storageServer/var/tests/mdata2.xml create mode 100644 livesupport/modules/storageServer/var/tests/mdata3.xml create mode 100644 livesupport/modules/storageServer/var/tests/question.wav create mode 100644 livesupport/modules/storageServer/var/tests/srch_cri1.xml create mode 100644 livesupport/modules/storageServer/var/tests/testStorage.xml create mode 100644 livesupport/modules/storageServer/var/xmlrpc/index.php create mode 100755 livesupport/modules/storageServer/var/xmlrpc/testRunner.sh create mode 100755 livesupport/modules/storageServer/var/xmlrpc/urldecode create mode 100644 livesupport/modules/storageServer/var/xmlrpc/xmlrpc.inc create mode 100644 livesupport/modules/storageServer/var/xmlrpc/xmlrpcs.inc create mode 100644 livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php create mode 100755 livesupport/modules/storageServer/var/xmlrpc/xr_cli_test.py diff --git a/livesupport/modules/storageServer/Makefile b/livesupport/modules/storageServer/Makefile new file mode 100644 index 000000000..0ef7c0d3e --- /dev/null +++ b/livesupport/modules/storageServer/Makefile @@ -0,0 +1,153 @@ +#------------------------------------------------------------------------------- +# Storage - file storage component +# Copyright (c) 2004 Media Development Loan Fund +# +# This file is part of the LiveSupport project. +# +# LiveSupport is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LiveSupport is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LiveSupport; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# Author : $Author: tomas $ +# Version : $Revision: 1.1 $ +# Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/storageServer/Attic/Makefile,v $ +# +# @configure_input@ +#------------------------------------------------------------------------------- + +#------------------------------------------------------------------------------- +# General command definitions +#------------------------------------------------------------------------------- +MKDIR = mkdir -p +RM = rm -f +RMDIR = rm -rf +DOXYGEN = doxygen + +#------------------------------------------------------------------------------- +# Misc +#------------------------------------------------------------------------------- + +MODULE_NAME = storageServer +TAR_C = tar -cj --exclude CVS --exclude '*~' -C ${BASE_DIR} -f +DIST_EXT = .tgz +DATE = `date +%y%m%d` + +#------------------------------------------------------------------------------- +# Basic directory and file definitions +#------------------------------------------------------------------------------- +#BASE_DIR = @builddir@ +BASE_DIR = . +DOC_DIR = ${BASE_DIR}/doc +DOXYGEN_DIR = ${DOC_DIR}/doxygen +ETC_DIR = ${BASE_DIR}/etc +INCLUDE_DIR = ${BASE_DIR}/include +LIB_DIR = ${BASE_DIR}/lib +SRC_DIR = ${BASE_DIR}/src +TMP_DIR = ${BASE_DIR}/tmp + +USR_DIR = ${BASE_DIR}/../../usr +USR_INCLUDE_DIR = ${USR_DIR}/include +USR_LIB_DIR = ${USR_DIR}/lib + +DOXYGEN_CONFIG = ${ETC_DIR}/doxygen.config + +HTTP_GROUP = nobody + +PHP_DIR = ${BASE_DIR}/var +INSTALL_DIR = ${PHP_DIR}/install +STOR_DIR = ${PHP_DIR}/stor +ACCESS_DIR = ${PHP_DIR}/access +BUFF_DIR = ${STOR_DIR}/buffer +TEST_RUNNER = ${PHP_DIR}/xmlrpc/testRunner.sh + +#------------------------------------------------------------------------------- +# Configuration parameters +#------------------------------------------------------------------------------- +#CPPFLAGS = @CPPFLAGS@ +#CXXFLAGS = @CXXFLAGS@ @DEFS@ -I${USR_INCLUDE_DIR} -I${INCLUDE_DIR} -I${TMP_DIR}\ +# -pedantic -Wall +#LDFLAGS = @LDFLAGS@ -L${USR_LIB_DIR} -L${LIB_DIR} + + +#------------------------------------------------------------------------------- +# Dependencies +#------------------------------------------------------------------------------- +#HELLO_LIB_OBJS = ${TMP_DIR}/Hello.o +#TEST_RUNNER_OBJS = ${TMP_DIR}/HelloTest.o ${TMP_DIR}/TestRunner.o + + +#------------------------------------------------------------------------------- +# Targets +#------------------------------------------------------------------------------- +.PHONY: all dir_setup doc clean docclean depclean distclean dist db_init db_clean + +all: dir_setup db_init + +dir_setup: ${DOXYGEN_DIR} ${STOR_DIR} ${ACCESS_DIR} + +doc: + ${DOXYGEN} ${DOXYGEN_CONFIG} + +clean: db_clean + +docclean: + ${RMDIR} ${DOXYGEN_DIR}/html + +depclean: clean + +dist: + ${TAR_C} ${MODULE_NAME}${DATE}${DIST_EXT} * + +distclean: clean docclean + +check: all ${TEST_RUNNER} + ${TEST_RUNNER} + +#------------------------------------------------------------------------------- +# Specific targets +#------------------------------------------------------------------------------- +db_init: + cd var/install; php -q install.php + +db_clean: + cd var/install; php -q uninstall.php + +${TMP_DIR}: + ${MKDIR} ${TMP_DIR} + +${DOXYGEN_DIR}: + ${MKDIR} ${DOXYGEN_DIR} + +${STOR_DIR}: + ${MKDIR} ${STOR_DIR} + chown .${HTTP_GROUP} ${STOR_DIR} + chmod g+ws ${STOR_DIR} + +${ACCESS_DIR}: + ${MKDIR} ${ACCESS_DIR} + chown .${HTTP_GROUP} ${ACCESS_DIR} + chmod g+ws ${ACCESS_DIR} + +${BUFF_DIR}: + ${MKDIR} ${BUFF_DIR} + chmod g+w ${BUFF_DIR} + +${TEST_RUNNER}: + +#------------------------------------------------------------------------------- +# Pattern rules +#------------------------------------------------------------------------------- +#${TMP_DIR}/%.o : ${SRC_DIR}/%.cxx +# ${CXX} ${CPPFLAGS} ${CXXFLAGS} -c -o $@ $< + diff --git a/livesupport/modules/storageServer/etc/doxygen.config b/livesupport/modules/storageServer/etc/doxygen.config new file mode 100644 index 000000000..b7ab0713a --- /dev/null +++ b/livesupport/modules/storageServer/etc/doxygen.config @@ -0,0 +1,1144 @@ +#------------------------------------------------------------------------------- +# doxygen.config +# Copyright (c) 2004 Media Development Loan Fund +# +# This file is part of the LiveSupport project. +# +# LiveSupport is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LiveSupport is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LiveSupport; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# Author : $Author: tomas $ +# Version : $Revision: 1.1 $ +# Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/storageServer/etc/doxygen.config,v $ +#------------------------------------------------------------------------------- + +# Doxyfile 1.3.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = LiveSupport + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1.0 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with English messages), Korean, Korean-en, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is used +# as the annotated text. Otherwise, the brief description is used as-is. If left +# blank, the following values are used ("$name" is automatically replaced with the +# name of the entity): "The $name class" "The $name widget" "The $name file" +# "is" "provides" "specifies" "contains" "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = YES + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +#EXTRACT_ALL = NO +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = var var/xmlrpc ../alib/var ../alib/var/xmlrpc + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc + +FILE_PATTERNS = *.php + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = include + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superseded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/livesupport/modules/storageServer/var/GreenBox.php b/livesupport/modules/storageServer/var/GreenBox.php new file mode 100644 index 000000000..847b1be31 --- /dev/null +++ b/livesupport/modules/storageServer/var/GreenBox.php @@ -0,0 +1,993 @@ +filesTable = $config['tblNamePrefix'].'files'; + $this->mdataTable = $config['tblNamePrefix'].'mdata'; + $this->accessTable= $config['tblNamePrefix'].'access'; + $this->storageDir = $config['storageDir']; + $this->accessDir = $config['accessDir']; + $this->dbc->setErrorHandling(PEAR_ERROR_RETURN); + $this->rootId = $this->getRootNode(); + $this->storId = $this->wd = + $this->getObjId('StorageRoot', $this->rootId); + $this->dbc->setErrorHandling(); + } + + + /* ======================================================= public methods */ + + /** + * Create new folder + * + * @param parid int, parent id + * @param folderName string, name for new folder + * @param sessid string, session id + * @return id of new folder + * @exception PEAR::error + */ + function createFolder($parid, $folderName, $sessid='') + { + if(($res = $this->_authorize('write', $parid, $sessid)) !== TRUE) + return $res; + return $this->addObj($folderName , 'Folder', $parid); + } + + /** + * Store new file in the storage + * + * @param parid int, parent id + * @param fileName string, name for new file + * @param mediaFileLP string, local path of media file + * @param mdataFileLP string, local path of metadata file + * @param sessid string, session id + * @return int + * @exception PEAR::error + */ + function putFile($parid, $fileName, + $mediaFileLP, $mdataFileLP, $sessid='') + { + if(($res = $this->_authorize('write', $parid, $sessid)) !== TRUE) + return $res; + $name = "$fileName"; + $id = $this->addObj($name , 'File', $parid); + $ac =& StoredFile::insert( + &$this, $id, $name, $mediaFileLP, $mdataFileLP + ); + if(PEAR::isError($ac)) return $ac; + return $id; + } + + /** + * Return raw media file.
+ * will be probably removed from API
+ * see access() method + * + * @param id int, virt.file's local id + * @param sessid string, session id + * @return file + */ + function getFile($id, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::getFile: probably obsolete API function'); + if(($res = $this->_authorize('read', $id, $sessid)) !== TRUE) + return $res; + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)) return $ac; + else{ + $fname = $ac->accessRawMediaData(); +# readfile($fname); + return join('', file($fname)); + $fname = $ac->releaseRawMediaData(); + } + } + + /** + * Analyze media file for internal metadata information + * + * @param id int, virt.file's local id + * @param sessid string, session id + * @return array + */ + function analyzeFile($id, $sessid='') + { + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)) return $ac; + else{ +# echo"
\n"; print_r($ac); exit;
+            $ia = $ac->analyzeMediaFile();
+            return $ia;
+        }
+    }
+    
+    /**
+     *  Create and return access link to media file
+     *
+     *  @param id int, virt.file's local id
+     *  @param sessid string, session id
+     *  @return filename as string or PEAR::error
+     */
+    function access($id, $sessid='')
+    {
+        if(($res = $this->_authorize('read', $id, $sessid)) !== TRUE)
+            return $res;
+        $ac =& StoredFile::recall(&$this, $id);
+        if(PEAR::isError($ac)) return $ac;
+        else{
+            $fname = $ac->accessRawMediaData($sessid);
+            return $fname;
+        }
+    }
+
+    /**
+     *  Release access link to media file
+     *
+     *  @param id int, virt.file's local id
+     *  @param sessid string, session id
+     *  @return boolean or PEAR::error
+     */
+    function release($id, $sessid='')
+    {
+        if(($res = $this->_authorize('read', $id, $sessid)) !== TRUE)
+            return $res;
+        $ac =& StoredFile::recall(&$this, $id);
+        if(PEAR::isError($ac)) return $ac;
+        else{
+            $res = $ac->releaseRawMediaData($sessid);
+            return $res;
+        }
+    }
+
+    /**
+     *  Rename file
+     *
+     *  @param id int, virt.file's local id
+     *  @param newName string
+     *  @param sessid string, session id
+     *  @return boolean or PEAR::error
+     */
+    function renameFile($id, $newName, $sessid='')
+    {
+        $parid = $this->getParent($id);
+        if(($res = $this->_authorize('write', $parid, $sessid)) !== TRUE)
+            return $res;
+        $ac =& StoredFile::recall(&$this, $id);
+        if(PEAR::isError($ac)) return $ac;
+        else{
+            $res = $ac->rename($newName);
+            if(PEAR::isError($res)) return $res;
+            return $this->renameObj($id, $newName);
+        }
+    }
+
+    /**
+     *  Move file
+     *
+     *  @param id int, virt.file's local id
+     *  @param did int, destination folder local id
+     *  @param sessid string, session id
+     *  @return boolean or PEAR::error
+     */
+    function moveFile($id, $did, $sessid='')
+    {
+        if($this->getObjType($did) !== 'Folder')
+            return PEAR::raiseError(
+                'GreenBox::moveFile: destination is not folder', GBERR_WRTYPE
+            );
+        if(($res = $this->_authorize(
+            array('read', 'write'), array($id, $did), $sessid
+        )) !== TRUE) return $res;
+        $this->_relocateSubtree($id, $did);
+    }
+
+    /**
+     *  Copy file
+     *
+     *  @param id int, virt.file's local id
+     *  @param did int, destination folder local id
+     *  @param sessid string, session id
+     *  @return boolean or PEAR::error
+     */
+    function copyFile($id, $did, $sessid='')
+    {
+        if($this->getObjType($did)!=='Folder')
+            return PEAR::raiseError(
+                'GreenBox::copyFile: destination is not folder', GBERR_WRTYPE
+            );
+        if(($res = $this->_authorize(
+            array('read', 'write'), array($id, $did), $sessid
+        )) !== TRUE) return $res;
+        return $this->_copySubtree($id, $did);
+    }
+
+    /**
+     *  Delete file
+     *
+     *  @param id int, virt.file's local id
+     *  @param sessid int
+     *  @return true or PEAR::error
+     */
+    function deleteFile($id, $sessid='')
+    {
+        $parid = $this->getParent($id);
+        if(($res = $this->_authorize('write', $parid, $sessid)) !== TRUE)
+            return $res;
+        $res = $this->removeObj($id);
+        if(PEAR::isError($res)) return $res;
+        return TRUE;
+    }
+
+    /* ---------------------------------------------- replicas, versions etc. */
+
+    /**
+     *  Create replica.
+ * TODO: NOT FINISHED + * + * @param id int, virt.file's local id + * @param did int, destination folder local id + * @param replicaName string, name of new replica + * @param sessid string, session id + * @return int, local id of new object + */ + function createReplica($id, $did, $replicaName='', $sessid='') + { + return PEAR::raiseError( + 'GreenBox::createVersion: not implemented', GBERR_NOTIMPL + ); + // --- + if($this->getObjType($did)!=='Folder') + return PEAR::raiseError( + 'GreenBox::createReplica: dest is not folder', GBERR_WRTYPE + ); + if(($res = $this->_authorize( + array('read', 'write'), array($id, $did), $sessid + )) !== TRUE) return $res; + if($replicaName=='') $replicaName = $this->getObjName($id); + while(($exid = $this->getObjId($replicaName, $did))<>'') + { $replicaName.='_R'; } + $rid = $this->addObj($replicaName , 'Replica', $did, 0, $id); + if(PEAR::isError($rid)) return $rid; +# $this->addMdata($this->_pathFromId($rid), 'isReplOf', $id, $sessid); + return $rid; + } + + /** + * Create version.
+ * TODO: NOT FINISHED + * + * @param id int, virt.file's local id + * @param did int, destination folder local id + * @param versionLabel string, name of new version + * @param sessid string, session id + * @return int, local id of new object + */ + function createVersion($id, $did, $versionLabel, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::createVersion: not implemented', GBERR_NOTIMPL + ); + } + + + /* ------------------------------------------------------------- metadata */ + + /** + * Update metadata tree + * + * @param id int, virt.file's local id + * @param mdataFile string, local path of metadata XML file + * @param sessid string, session id + * @return boolean or PEAR::error + */ + function updateMetadata($id, $mdataFile, $sessid='') + { + if(($res = $this->_authorize('write', $id, $sessid)) !== TRUE) + return $res; + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)) return $ac; + else{ + return $ac->updateMetaData($mdataFile); + } + } + + /** + * Update object namespace and value of one metadata record + * + * @param id int, virt.file's local id + * @param mdid int, metadata record id + * @param object string, object value, e.g. title string + * @param objns string, object namespace prefix, have to be defined + * in file's metadata (or reserved prefix) + * @param sessid string, session id + * @return boolean or PEAR::error + * @see MetaData + */ + function updateMetadataRecord($id, $mdid, $object, $objns='_L', $sessid='') + { + if(($res = $this->_authorize('write', $id, $sessid)) !== TRUE) + return $res; + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)) return $ac; + else{ + return $ac->updateMetaDataRecord($mdid, $object, $objns); + } + } + + /** + * Add single metadata record.
+ * TODO: NOT FINISHED
+ * Params could be changed! + * + * @param id int, virt.file's local id + * @param propertyName string + * @param propertyValue string + * @param sessid string, session id + * @return boolean or PEAR::error + * @see MetaData + */ + function addMetaDataRecord($id, $propertyName, + $propertyValue, $sessid='') + { + //if(($res = $this->_authorize('write', $id, $sessid)) !== TRUE) + // return $res; + return PEAR::raiseError( + 'GreenBox::addMetaDataRecord: not implemented', GBERR_NOTIMPL + ); + } + + /** + * Get metadata XML tree as string + * + * @param id int, virt.file's local id + * @param sessid string, session id + * @return string or PEAR::error + */ + function getMdata($id, $sessid='') + { + if(($res = $this->_authorize('read', $id, $sessid)) !== TRUE) + return $res; + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)) return $ac; + else{ + return $ac->getMetaData(); + } + } + + /** + * Search in local metadata database.
+ * TODO: NOT FINISHED
+ * Should support structured queries, e.g.:
+ * XML file with the structure as metadata, but + * with SQL LIKE terms instead of metadata values.
+ * Some standard query format would be better, + * but I've not found it yet. + * + * @param searchData string, search query - + * only one SQL LIKE term supported now. + * It will be searched in all literal object values + * in metadata database + * @param sessid string, session id + * @return array of gunid strings + */ + function localSearch($searchData, $sessid='') + { + $ftsrch = $searchData; + $res = $this->dbc->getAll("SELECT md.gunid as gunid + FROM {$this->filesTable} f, {$this->mdataTable} md + WHERE f.gunid=md.gunid AND md.objns='_L' AND + md.object like '$ftsrch' + GROUP BY md.gunid + "); + if(!is_array($res)) $res = array(); +/* + if(!(count($res)>0)) + return PEAR::raiseError( + 'GreenBox::localSearch: no items found', GBERR_NONE + ); +*/ + return $res; + } + + /* -------------------------------------------- remote repository methods */ + + /** + * Upload file to remote repository + * + * @param id int, virt.file's local id + * @param sessid string, session id + * @return string - transfer id or PEAR::error + */ + function uploadFile($id, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::uploadFile: not implemented', GBERR_NOTIMPL + ); + } + + /** + * Download file from remote repository + * + * @param id int, virt.file's local id + * @param parid int, destination folder local id + * @param sessid string, session id + * @return string - transfer id or PEAR::error + */ + function downloadFile($id, $parid, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::downloadFile: not implemented', GBERR_NOTIMPL + ); + } + + /** + * Get status of asynchronous transfer + * + * @param transferId int, id of asynchronous transfer + * returned by uploadFile or downloadFile methods + * @param sessid string, session id + * @return string or PEAR::error + */ + function getTransferStatus($transferId, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::getTransferStatus: not implemented', GBERR_NOTIMPL + ); + } + + /** + * Search in central metadata database + * + * @param searchData string, search query - see localSearch method + * @param sessid string, session id + * @return string - job id or PEAR::error + */ + function globalSearch($searchData, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::globalSearch: not implemented', GBERR_NOTIMPL + ); + } + + /** + * Get results from asynchronous search + * + * @param transferId int, transfer id returned by + * @param sessid string, session id + * @return array with results or PEAR::error + */ + function getSearchResults($transferId, $sessid='') + { + return PEAR::raiseError( + 'GreenBox::getSearchResults: not implemented', GBERR_NOTIMPL + ); + } + + + /* --------------------------------------------------------- info methods */ + + /** + * List files in folder + * + * @param id int, local id of folder + * @param sessid string, session id + * @return array + */ + function listFolder($id, $sessid='') + { + if(($res = $this->_authorize('read', $id, $sessid)) !== TRUE) + return $res; + if($this->getObjType($id)!=='Folder') + return PEAR::raiseError( + 'GreenBox::listFolder: not a folder', GBERR_NOTF + ); + $a = $this->getDir($id, 'id, name, type, param as target', 'name'); + return $a; + } + + /** + * List files in folder + * + * @param id int, local id of object + * @param relPath string, relative path + * @return array + */ + function getObjIdFromRelPath($id, $relPath='.') + { +# $a = $this->getDir($id, 'id, name, type, param as target', 'name'); + $a = split('/', $relPath); + if($this->getObjType($id)!=='Folder') $nid = $this->getparent($id); + else $nid = $id; + foreach($a as $i=>$item){ + switch($item){ + case".": + break; + case"..": + $nid = $this->getparent($nid); + break; + case"": + break; + default: + $nid = $this->getObjId($item, $nid); + } +# $a[$i] = array('o'=>$item, 'n'=>($nid==null ? 'NULL' : $nid)); + } +# return $a; + return $nid; + } + + + /* ---------------------------------------------------- redefined methods */ + + /** + * Logout and destroy session + * + * @param sessid string + * @return true/err + */ + function logout($sessid) + { + $acfa = $this->dbc->getAll("SELECT * FROM {$this->accessTable} + WHERE sessid='$sessid'"); + if(PEAR::isError($acfa)) return $acfa; + foreach($acfa as $i=>$acf){ + $ac =& StoredFile::recallFromLink(&$this, $acf['tmplink'], $sessid); + $ac->releaseRawMediaData($sessid); + } + parent::logout($sessid); + } + + /** + * Add new user with home folder + * + * @param login string + * @param pass string OPT + * @return int/err + */ + function addSubj($login, $pass=NULL) + { + $uid = parent::addSubj($login, $pass); + if(PEAR::isError($uid)) return $uid; + $fid = $this->addObj($login , 'Folder', $this->storId); + if(PEAR::isError($fid)) return $fid; + $res = $this->addPerm($uid, '_all', $fid, 'A'); + if(PEAR::isError($res)) return $res; + return $uid; + } + /** + * Remove user and his home folder + * + * @param login string + * @param uid int OPT + * @return boolean/err + */ + function removeSubj($login, $uid=NULL) + { + $res = parent::removeSubj($login, $pass); + if(PEAR::isError($res)) return $res; + $id = $this->getObjId($login, $this->storId); + if(PEAR::isError($id)) return $id; + $res = $this->removeObj($id); + if(PEAR::isError($res)) return $res; + return TRUE; + } + + /** + * Get file's path in virtual filesystem + * + * @param id int + * @return array + */ + function getPath($id) + { + $pa = parent::getPath($id, 'id, name, type'); array_shift($pa); + return $pa; + } + + /* ==================================================== "private" methods */ + + /** + * Copy virtual file.
+ * Redefined from parent class. + * + * @return id + */ + function copyObj($id, $newParid, $after='') + { + $nid = parent::copyObj($id, $newParid, $after=''); + if($this->getObjType($id)==='File'){ + $ac =& StoredFile::recall(&$this, $id); + if(PEAR::isError($ac)){ return $ac; } + $ac2 =& StoredFile::copyOf(&$ac, $nid); + } + return $nid; + } + /** + * Optionaly remove virtual file with the same name and add new one.
+ * Redefined from parent class. + * + * @return id + */ + function addObj($name, $type, $parid=1, $aftid=NULL, $param='') + { + if(!is_null($exid = $this->getObjId($name, $parid))) + { $this->removeObj($exid); } + return parent::addObj($name, $type, $parid, $aftid, $param); + } + + /** + * Remove virtual file.
+ * Redefined from parent class. + * + * @param id int, local id of removed object + * @return true or PEAR::error + */ + function removeObj($id) + { + switch($this->getObjType($id)){ + case"File": + $ac =& StoredFile::recall(&$this, $id); + if(!PEAR::isError($ac)){ + $ac->delete(); + } + parent::removeObj($id); + break; + case"Folder": + parent::removeObj($id); + break; + case"Replica": + parent::removeObj($id); + break; + default: + } + return TRUE; + } + + /** + * Check authorization - auxiliary method + * + * @param acts array of actions + * @param pars array of parameters - e.g. ids + * @param sessid string, session id + * @return true or PEAR::error + */ + function _authorize($acts, $pars, $sessid='') + { + $userid = $this->getSessUserId($sessid); + if(!is_array($pars)) $pars = array($pars); + if(!is_array($acts)) $acts = array($acts); + $perm = true; + foreach($acts as $i=>$action){ + $res = $this->checkPerm($userid, $action, $pars[$i]); + if(PEAR::isError($res)) return $res; + $perm = $perm && $res; + } + if($perm) return TRUE; + return PEAR::raiseError("GreenBox::$method: access denied", GBERR_DENY); + } + + /** + * Get local id from global id + * + * @param gunid string global id + * @return int local id + */ + function _idFromGunid($gunid) + { + return $this->dbc->getOne( + "SELECT id FROM {$this->filesTable} WHERE gunid='$gunid'" + ); + } + + /* =============================================== test and debug methods */ + /** + * dump + * + */ + function dump($id='', $indch=' ', $ind='', $format='{name}') + { + if($id=='') $id = $this->storId; + return parent::dump($id, $indch, $ind, $format); + } + + /** + * + * + */ + function dumpDir($id='', $format='$o["name"]') + { + if($id=='') $id = $this->storId; + $arr = $this->getDir($id, 'id,name'); +// if($this->doDebug){ $this->debug($arr); exit; } + $arr = array_map(create_function('$o', 'return "'.$format.'";'), $arr); + return join('', $arr); + } + + /** + * + * + */ + function debug($va) + { + echo"
\n"; print_r($va); #exit;
+    }
+
+    /**
+     *  deleteData
+     *
+     *  @return void
+     */
+    function deleteData()
+    {
+//        $this->dbc->query("DELETE FROM {$this->filesTable}");
+        $ids = $this->dbc->getAll("SELECT id FROM {$this->filesTable}");
+        if(is_array($ids)) foreach($ids as $i=>$item){
+            $this->removeObj($item['id']);
+        }
+        parent::deleteData();
+        $this->initData();
+    }
+    /**
+     *  testData
+     *
+     */
+    function testData($d='')
+    {
+        $exdir = dirname(getcwd()).'/tests';
+        $s = $this->sessid;
+        $o[] = $this->addSubj('test1', 'a');
+        $o[] = $this->addSubj('test2', 'a');
+        $o[] = $this->addSubj('test3', 'a');
+        $o[] = $this->addSubj('test4', 'a');
+
+        $o[] = $t1hd = $this->getObjId('test1', $this->storId);
+        $o[] = $t1d1 = $this->createFolder($t1hd, 'test1_folder1', $s);
+        $o[] = $this->createFolder($t1hd, 'test1_folder2', $s);
+        $o[] = $this->createFolder($t1d1, 'test1_folder1_1', $s);
+        $o[] = $t1d12 = $this->createFolder($t1d1, 'test1_folder1_2', $s);
+
+        $o[] = $t2hd = $this->getObjId('test2', $this->storId);
+        $o[] = $this->createFolder($t2hd, 'test2_folder1', $s);
+
+        $o[] = $this->putFile($t1hd, 'file1.mp3', "$exdir/ex1.mp3", '', $s);
+        $o[] = $this->putFile($t1d12 , 'file2.wav', "$exdir/ex2.wav", '', $s);
+/*
+*/
+        $this->tdata['storage'] = $o;
+    }
+
+    /**
+     *  test
+     *
+     */
+    function test()
+    {
+//        if(PEAR::isError($p = parent::test())) return $p;
+        $this->deleteData();
+        $this->login('root', $this->config['tmpRootPass']);
+        $this->testData();
+        $this->logout($this->sessid);
+        $this->test_correct = "    StorageRoot
+        root
+        test1
+            test1_folder1
+                test1_folder1_1
+                test1_folder1_2
+                    file2.wav
+            test1_folder2
+            file1.mp3
+        test2
+            test2_folder1
+        test3
+        test4
+";
+        $this->test_dump = $this->dumpTree($this->storId);
+        if($this->test_dump==$this->test_correct)
+            { $this->test_log.="Storage: OK\n"; return true; }
+        else PEAR::raiseError('GreenBox::test:', 1, PEAR_ERROR_DIE, '%s'.
+            "
\ncorrect:\n.{$this->test_correct}.\n".
+            "dump:\n.{$this->test_dump}.\n
\n"); + } + + /** + * initData - initialize + * + */ + function initData() + { + $this->rootId = $this->getRootNode(); + $this->storId = $this->wd = + $this->addObj('StorageRoot', 'Folder', $this->rootId); + $rootUid = parent::addSubj('root', $this->config['tmpRootPass']); + $this->login('root', $this->config['tmpRootPass']); + $res = $this->addPerm($rootUid, '_all', $this->rootId, 'A'); + $fid = $this->createFolder($this->storId, 'root', $this->sessid); +# $id = $this->dbc->nextId("{$this->mdataTable}_id_seq"); + $this->logout($this->sessid); + } + /** + * install - create tables + * + */ + function install() + { + parent::install(); + $this->dbc->query("CREATE TABLE {$this->filesTable} ( + id int not null, + gunid char(32) not null, + name varchar(255) not null default'', + type varchar(255) not null default'', + currentlyAccessing int not null default 0 + )"); + $this->dbc->query("CREATE UNIQUE INDEX {$this->filesTable}_id_idx + ON {$this->filesTable} (id)"); + $this->dbc->query("CREATE UNIQUE INDEX {$this->filesTable}_gunid_idx + ON {$this->filesTable} (gunid)"); + $this->dbc->query("CREATE INDEX {$this->filesTable}_name_idx + ON {$this->filesTable} (name)"); + + $this->dbc->createSequence("{$this->mdataTable}_id_seq"); + $this->dbc->query("CREATE TABLE {$this->mdataTable} ( + id int not null, + gunid char(32), + subjns varchar(255), -- subject namespace shortcut/uri + subject varchar(255) not null default '', + predns varchar(255), -- predicate namespace shortcut/uri + predicate varchar(255) not null, + predxml char(1) not null default 'T', -- Tag or Attribute + objns varchar(255), -- object namespace shortcut/uri + object text + )"); + $this->dbc->query("CREATE UNIQUE INDEX {$this->mdataTable}_id_idx + ON {$this->mdataTable} (id)"); + $this->dbc->query("CREATE INDEX {$this->mdataTable}_gunid_idx + ON {$this->mdataTable} (gunid)"); + $this->dbc->query("CREATE INDEX {$this->mdataTable}_subj_idx + ON {$this->mdataTable} (subjns, subject)"); + $this->dbc->query("CREATE INDEX {$this->mdataTable}_pred_idx + ON {$this->mdataTable} (predns, predicate)"); + + $this->dbc->query("CREATE TABLE {$this->accessTable} ( + gunid char(32) not null, + sessid char(32) not null, + tmpLink varchar(255) not null default'', + ts timestamp + )"); + $this->dbc->query("CREATE INDEX {$this->accessTable}_acc_idx + ON {$this->accessTable} (tmpLink, sessid)"); + if(!file_exists("{$this->storageDir}/buffer")){ + mkdir("{$this->storageDir}/buffer", 0775); + } + $this->initData(); + } + /** + * id subjns subject predns predicate objns object + * y1 literal xmbf NULL namespace literal http://www.sotf.org/xbmf + * x1 gunid xbmf contributor NULL NULL + * x2 mdid x1 xbmf role literal Editor + * + * predefined shortcuts: + * _L = literal + * _G = gunid (global id of media file) + * _I = mdid (local id of metadata record) + * _nssshortcut = namespace shortcut definition + * _blank = blank node + */ + + /** + * uninstall + * + * @return void + */ + function uninstall() + { + $this->dbc->query("DROP TABLE {$this->mdataTable}"); + $this->dbc->dropSequence("{$this->mdataTable}_id_seq"); + $this->dbc->query("DROP TABLE {$this->filesTable}"); + $this->dbc->query("DROP TABLE {$this->accessTable}"); + $d = dir($this->storageDir); + while (is_object($d) && (false !== ($entry = $d->read()))){ + if(filetype("{$this->storageDir}/$entry")=='dir' && + $entry!='CVS' && strlen($entry)==3) + { + $dd = dir("{$this->storageDir}/$entry"); + while (false !== ($ee = $dd->read())){ + //if(substr($ee, -4)=='.mp3' || substr($ee, -4)=='.xml') + if(substr($ee, 0, 1)!=='.') + unlink("{$this->storageDir}/$entry/$ee"); + } + $dd->close(); + rmdir("{$this->storageDir}/$entry"); + } + } + if(is_object($d)) $d->close(); + if(file_exists("{$this->storageDir}/buffer")){ + $d = dir("{$this->storageDir}/buffer"); + while (false !== ($entry = $d->read())) if(substr($entry,0,1)!='.') + { unlink("{$this->storageDir}/buffer/$entry"); } + $d->close(); + rmdir("{$this->storageDir}/buffer"); + } + parent::uninstall(); + } + + /** + * Aux logging for debug + * + * @param msg string - log message + */ + function debugLog($msg) + { + $fp=fopen("{$this->storageDir}/log", "a") or die("Can't write to log\n"); + fputs($fp, date("H:i:s").">$msg<\n"); + fclose($fp); + } +} +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/LocStor.php b/livesupport/modules/storageServer/var/LocStor.php new file mode 100644 index 000000000..4c7301917 --- /dev/null +++ b/livesupport/modules/storageServer/var/LocStor.php @@ -0,0 +1,124 @@ +getCode()){ + case GBERR_FILENEX: + case GBERR_FOBJNEX: + return FALSE; + break; + default: return $ac; + } + } + if(($res = $this->_authorize('read', $ac->getId(), $sessid)) !== TRUE) + return $res; + return $ac->exists(); + } + function storeAudioClip($sessid, $gunid, $mediaFileLP, $mdataFileLP) + { + // test if specified gunid exists: + $ac =& StoredFile::recall(&$this, '', $gunid); + if(!PEAR::isError($ac)){ + // gunid exists - do replace + if(($res = $this->_authorize( + 'write', $ac->getId(), $sessid + )) !== TRUE) return $res; + if($ac->isAccessed()){ + return PEAR::raiseError( + 'LocStor.php: storeAudioClip: is accessed' + ); + } + $res = $ac->replace( + $ac->getId(), $ac->name,$mediaFileLP, $mdataFileLP + ); + if(PEAR::isError($res)) return $res; + }else{ + // gunid doesn't exists - do insert + $tmpid = uniqid(''); + $parid = $this->getObjId( + $this->getSessLogin($sessid), $this->storId + ); + if(PEAR::isError($parid)) return $parid; + if(($res = $this->_authorize('write', $parid, $sessid)) !== TRUE) + return $res; + $oid = $this->addObj($tmpid , 'File', $parid); + if(PEAR::isError($oid)) return $oid; + $ac =& StoredFile::insert( + &$this, $oid, '', $mediaFileLP, $mdataFileLP + ); + if(PEAR::isError($ac)) return $ac; +// $this->debugLog("parid=$parid, gunid={$ac->gunid}, +// mediaFileLP=$mediaFileLP"); + $res = $this->renameFile($oid, $ac->gunid, $sessid); + if(PEAR::isError($res)) return $res; + } + return $ac->gunid; + } + function deleteAudioClip($sessid, $gunid) + { + $ac =& StoredFile::recall(&$this, '', $gunid); + if(PEAR::isError($ac)) return $ac; + if(($res = $this->_authorize('write', $ac->getId(), $sessid)) !== TRUE) + return $res; + $res = $this->deleteFile($ac->getId(), $sessid); + if(PEAR::isError($res)) return $res; + return TRUE; + } + function updateAudioClipMetadata($sessid, $gunid, $mdataFileLP) + { + $ac =& StoredFile::recall(&$this, '', $gunid); + if(PEAR::isError($ac)) return $ac; + if(($res = $this->_authorize('write', $ac->getId(), $sessid)) !== TRUE) + return $res; + return $ac->replaceMetaData($mdataFileLP); + } + function accessRawAudioData($sessid, $gunid) + { + $ac =& StoredFile::recall(&$this, '', $gunid); + if(PEAR::isError($ac)) return $ac; + if(($res = $this->_authorize('read', $ac->getId(), $sessid)) !== TRUE) + return $res; + return $ac->accessRawMediaData($sessid); + } + function releaseRawAudioData($sessid, $tmpLink) + { + $ac =& StoredFile::recallFromLink(&$this, $tmpLink, $sessid); + if(PEAR::isError($ac)) return $ac; + if(($res = $this->_authorize('read', $ac->getId(), $sessid)) !== TRUE) + return $res; + return $ac->releaseRawMediaData($sessid); + } + function searchMetadata($sessid, $criteria) + { + $res = $this->localSearch($criteria, $sessid); + return $res; + } + function getAudioClip($sessid, $gunid) + { + $ac =& StoredFile::recall(&$this, '', $gunid); + if(PEAR::isError($ac)) return $ac; + if(($res = $this->_authorize('read', $ac->getId(), $sessid)) !== TRUE) + return $res; + $md = $this->getMdata($ac->getId(), $sessid); + if(PEAR::isError($md)) return $md; + return $md; + } +} +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/MetaData.php b/livesupport/modules/storageServer/var/MetaData.php new file mode 100644 index 000000000..e191b4d5c --- /dev/null +++ b/livesupport/modules/storageServer/var/MetaData.php @@ -0,0 +1,454 @@ + + * Store metadata tree in relational database.
+ * requires DOMXML support in PHP! + * + * @see StoredFile + */ +class MetaData{ + /** + * Constructor + * + * @param gb reference to GreenBox object + * @param gunid string, global unique id + * @return this + */ + function MetaData(&$gb, $gunid) + { + $this->dbc =& $gb->dbc; + $this->mdataTable = $gb->mdataTable; + $this->gunid = $gunid; + $this->exists = $this->dbCheck($gunid); + } + /** + * Parse and store metadata from XML file or XML string + * + * @param mdata string, local path to metadata XML file or XML string + * @param loc string 'file'|'string' + * @return true or PEAR::error + */ + function insert($mdata, $loc='file') + { + if($this->exists) return FALSE; + $res = $this->storeXMLDoc($mdata, $loc); + if(PEAR::isError($res)) return $res; + $this->exists = TRUE; + return TRUE; + } + /** + * Parse and update metadata + * + * @param mdata string, local path to metadata XML file or XML string + * @param loc string 'file'|'string' + * @return true or PEAR::error + */ + function update($mdata, $loc='file') + { + if(!$this->exists) return FALSE; + $res = $this->storeXMLDoc($mdata, $loc, 'update'); + if(PEAR::isError($res)) return $res; + $this->exists = TRUE; + return TRUE; + } + /** + * Call delete and insert + * + * @param mdata string, local path to metadata XML file or XML string + * @param loc string 'file'|'string' + * @return true or PEAR::error + */ + function replace($mdata, $loc='file') + { + if($this->exists) $res = $this->delete(); + if(PEAR::isError($res)) return $res; + return $this->insert($mdata, $loc); + } + /** + * Return true if metadata exists + * + * @return boolean + */ + function exists() + { + return $this->exists; + } + /** + * Delete all file's metadata + * + * @return true or PEAR::error + */ + function delete() + { + $res = $this->dbc->query("DELETE FROM {$this->mdataTable} + WHERE gunid='{$this->gunid}'"); + if(PEAR::isError($res)) return $res; + $this->exists = FALSE; + return TRUE; + } + /** + * Return metadata XML string + * + * @return string + */ + function getMetaData() + { + return $this->genXMLDoc(); + } + + /** + * Check if there are any file's metadata in database + * + * @param gunid string, global unique id + * @return boolean + */ + function dbCheck($gunid) + { + $cnt = $this->dbc->getOne("SELECT count(*)as cnt + FROM {$this->mdataTable} WHERE gunid='$gunid'"); + if(PEAR::isError($cnt)) return $cnt; + return (intval($cnt) > 0); + } + + /** + * Parse and insert or update metadata XML to database + * + * @param mdata string, local path to metadata XML file or XML string + * @param loc string 'file'|'string' + * @param mode string 'insert'|'update' + * @return true or PEAR::error + */ + function storeXMLDoc($mdata='', $loc='file', $mode='insert') + { + if($loc=='file' && file_exists($mdata)){ + $xml = domxml_open_file($mdata); + }else{ + $xml = domxml_open_mem($mdata); + } + $root = $xml->document_element(); +// $res = $this->dbTransaction($root, NULL, $mode); + // $root, $parid=NULL, $mode='insert' +// if(PEAR::isError($res)) return $res; + $this->dbc->query("BEGIN"); + if($mode == 'update') $this->nameSpaces = $this->readNamespaces(); + $res = $this->storeXMLNode($root, NULL, $mode); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + foreach($this->nameSpaces as $prefix=>$uri){ + $res = $this->storeRecord( + '_L', $prefix, NULL, '_namespace', 'T', '_L', $uri, $mode + ); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + return TRUE; + + + return $root; + } + + /** + * Read namespace definitions from database and return it as array + * + * @return array or PEAR::error + */ + function readNamespaces() + { + $nameSpaces = array(); + $arr = $this->dbc->getAll("SELECT subject, object + FROM {$this->mdataTable} + WHERE gunid='{$this->gunid}' + AND subjns='_L' + AND predns is null AND predicate='_namespace' + AND objns='_L' + "); + if(PEAR::isError($arr)) return $arr; + if(is_array($arr)){ + foreach($arr as $i=>$v){ + $nameSpaces[$v['subject']] = $v['object']; + } + } + return $nameSpaces; + } + + /** + * Process one node of metadata XML for insert or update.
+ * TODO: add support for other usable node types + * + * @param node DOM node object + * @param parid int, parent id + * @param mode 'insert'|'update' + * @return + */ + function storeXMLNode($node, $parid=NULL, $mode='insert') + { + switch($node->node_type()){ + case 1: // element + case 2: // attribute + $subjns = (is_null($parid)? '_G' : '_I'); + $subject = (is_null($parid)? $this->gunid : $parid); + if(!isset($this->nameSpaces[$node->prefix()])) + $this->nameSpaces[$node->prefix()] = $node->namespace_uri(); + $prefix = $node->prefix(); + break; + } + switch($node->node_type()){ + case 9: // document + $this->storeXMLNode($node->document_element(), $parid, $mode); + break; + case 1: // element + if($node->is_blank_node()) break; + $id = $this->storeRecord( + $subjns, $subject, $prefix, $node->node_name(), 'T', + '_blank', NULL, $mode + ); + if(PEAR::isError($id)) return $id; + if($node->has_attributes()){ + foreach($node->attributes() as $attr){ + $res = $this->storeXMLNode($attr, $id, $mode); + if(PEAR::isError($res)) return $res; + } + } + if($node->has_child_nodes()){ + foreach($node->child_nodes() as $child){ + $res = $this->storeXMLNode($child, $id, $mode); + if(PEAR::isError($res)) return $res; + } + } + break; + case 2: // attribute + $res = $this->storeRecord( + $subjns, $subject, $prefix, $node->node_name(), + 'A', '_L', $node->value(), $mode + ); + if(PEAR::isError($res)) return $res; + break; + case 3: // text + case 4: // cdata +# echo"T\n"; + if($node->is_blank_node()) break; + $objns_sql = "'_L'"; + $object_sql = "coalesce(object,'')||'".$node->node_value()."'"; + $res = $this->dbc->query(" + UPDATE {$this->mdataTable} + SET objns=$objns_sql, object=$object_sql + WHERE id='$parid' + "); + if(PEAR::isError($res)) return $res; + break; + case"5": case"6": case"7": case"8": + break; + default: + return PEAR::raiseError( + "MetaData::storeXMLNode: unsupported node type (". + $node->node_type().")" + ); + } + return TRUE; + } + + /** + * Update object namespace and value of one metadata record + * identified by metadata record id + * + * @param mdid int, metadata record id + * @param object string, object value, e.g. title string + * @param objns string, object namespace prefix, have to be defined + * in file's metadata + * @return true or PEAR::error + */ + function updateRecord($mdid, $object, $objns='_L') + { + $res = $this->dbc->query("UPDATE {$this->mdataTable} + SET objns = '$objns', object = '$object' + WHERE gunid = '{$this->gunid}' AND id='$mdid' + "); + if(PEAR::isError($res)) return $res; + return TRUE; + } + + /** + * Insert or update of one metadata record completely + * + * @param subjns string, subject namespace prefix, have to be defined + * in file's metadata (or reserved prefix) + * @param subject string, subject value, e.g. gunid + * @param predns string, predicate namespace prefix, have to be defined + * in file's metadata (or reserved prefix) + * @param predicate string, predicate value, e.g. name of DC element + * @param predxml string 'T'|'A' - XML tag or attribute + * @param objns string, object namespace prefix, have to be defined + * in file's metadata (or reserved prefix) + * @param object string, object value, e.g. title of song + * @param mode 'insert'|'update' + * @return int, new metadata record id + */ + function storeRecord($subjns, $subject, $predns, $predicate, $predxml='T', + $objns=NULL, $object=NULL, $mode='insert') + { + $predns_sql = (is_null($predns) ? "NULL":"'$predns'" ); + $objns_sql = (is_null($objns) ? "NULL":"'$objns'" ); + $object_sql = (is_null($object)? "NULL":"'$object'"); + if($mode == 'insert'){ + $id = $this->dbc->nextId("{$this->mdataTable}_id_seq"); + }else{ + $cond = "gunid = '{$this->gunid}' AND predns=$predns_sql + AND predicate='$predicate'"; + $id = $this->dbc->getOne("SELECT id FROM {$this->mdataTable} + WHERE $cond"); + } + if(PEAR::isError($id)) return $id; + if($mode == 'insert'){ + $res = $this->dbc->query(" + INSERT INTO {$this->mdataTable} + (id , gunid , subjns , subject , + predns , predicate , predxml , + objns , object + ) + VALUES + ($id, '{$this->gunid}', '$subjns', '$subject', + $predns_sql, '$predicate', '$predxml', + $objns_sql, $object_sql + ) + "); + }else{ + $res = $this->dbc->query(" + UPDATE {$this->mdataTable} + SET subjns = '$subjns', subject = '$subject', + objns = $objns_sql, object = $object_sql + WHERE id='$id' + "); +// WHERE $cond + } + if(PEAR::isError($res)) return $res; + return $id; + } + /** + * Generate XML document from metadata database + * + * @return string with XML document + */ + function genXMLDoc() + { + $domd =& domxml_new_xmldoc('1.0'); + $row = $this->dbc->getRow(" + SELECT * FROM {$this->mdataTable} + WHERE gunid='{$this->gunid}' + AND subjns='_G' AND subject='{$this->gunid}' + "); + if(PEAR::isError($row)) return $row; + if(is_null($row)) return PEAR::raiseError( + "MetaData::genXMLDoc: not exists ({$this->gunid})" + ); + $rr = $this->genXMLNode(&$domd, &$domd, $row); + if(PEAR::isError($rr)) return $rr; + return preg_replace("|]*)>|", "\n", $domd->dump_mem())."\n"; + } + + /** + * Generate XML element from database + * + * @param domd DOM document object + * @param xn DOM element object + * @param row array, database row with values for created element + * @return void + */ + function genXMLNode(&$domd, &$xn, $row) + { + if($row['predxml']=='T'){ + $nxn =& $domd->create_element($row['predicate']); + }else{ + $nxn =& $domd->create_attribute($row['predicate'], ''); + } + $xn->append_child(&$nxn); + $uri = $this->dbc->getOne(" + SELECT object FROM {$this->mdataTable} + WHERE gunid='{$this->gunid}' AND predicate='_namespace' + AND subjns='_L' AND subject='{$row['predns']}' + "); + if(!is_null($uri) && $uri !== ''){ + $root =& $domd->document_element(); + $root->add_namespace($uri, $row['predns']); + if($row['predns'] !== ''){ + $nxn->set_namespace($uri, $row['predns']); + } + } + if($row['object'] != 'NULL'){ + $tn =& $domd->create_text_node($row['object']); + $nxn->append_child(&$tn); + } + $this->genXMLTree(&$domd, &$nxn, $row['id']); + } + + /** + * Generate XML subtree from database + * + * @param domd DOM document object + * @param xn DOM element object + * @param parid parent id + * @return void + */ + function genXMLTree(&$domd, &$xn, $parid) + { + $qh = $this->dbc->query(" + SELECT * FROM {$this->mdataTable} + WHERE gunid='{$this->gunid}' AND subjns='_I' AND subject='$parid' + ORDER BY id + "); + if(PEAR::isError($qh)) return $qh; + while($row = $qh->fetchRow()){ + $this->genXMLNode(&$domd, &$xn, $row); + } + $qh->free(); + } + + /** + * Test method + * + * @return true or PEAR::error + */ + function test() + { + $res = $this->replace(getcwd().'/mdata2.xml'); + if(PEAR::isError($res)) return $res; + $res = $this->getMetaData(); + if(PEAR::isError($res)) return $res; + return TRUE; + } +} +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/RawMediaData.php b/livesupport/modules/storageServer/var/RawMediaData.php new file mode 100644 index 000000000..6a3973d0d --- /dev/null +++ b/livesupport/modules/storageServer/var/RawMediaData.php @@ -0,0 +1,236 @@ + + * + * @see StoredFile + */ + +/* ================== RawMediaData ================== */ +class RawMediaData{ + /** + * Constructor + * + * @param gunid string, global unique id + * @param resDir string, directory + * @return this + */ + function RawMediaData($gunid, $resDir) + { + $this->gunid = $gunid; + $this->resDir = $resDir; + $this->fname = $this->makeFname(); + $this->exists = file_exists($this->fname); + } + /** + * Insert media file to filesystem + * + * @param mediaFileLP string, local path + * @return true or PEAR::error + */ + function insert($mediaFileLP) + { + if($this->exists) return FALSE; + @umask(0002); + if(@copy($mediaFileLP, $this->fname)){ +// @chmod($this->fname, 0775); + $this->exists = TRUE; + return TRUE; + }else{ + @unlink($this->fname); + $this->exists = FALSE; + return PEAR::raiseError( + "RawMediaData::insert: file save failed", GBERR_FILEIO + ); + } + } + /** + * Delete and insert media file + * + * @param mediaFileLP string, local path + * @return true or PEAR::error + */ + function replace($mediaFileLP) + { + if($this->exists) $r = $this->delete(); + if(PEAR::isError($r)) return $r; + return $this->insert($mediaFileLP); + } + /** + * Return true if file corresponding to the object exists + * + * @return boolean + */ + function exists() + { + return $this->exists; + } + /** + * Return filename + * + * @return string + */ + function getFname() + { + return $this->fname; + } + /** + * Make access symlink to the media file + * + * @param accLinkName string, access symlink name + * @return string, access symlink name + */ + function access($accLinkName) + { + if(!$this->exists) return FALSE; + if(file_exists($accLinkName)) return $accLinkName; + if(@symlink($this->fname, $accLinkName)){ +// @chmod($accLinkName, 0775); + return $accLinkName; + }else return PEAR::raiseError( + "RawMediaData::access: symlink create failed ($accLinkName)", + GBERR_FILEIO + ); + } + /** + * Delete access symlink + * + * @param accLinkName string, access symlink name + * @return boolean or PEAR::error + */ + function release($accLinkName) + { + if(!$this->exists) return FALSE; + if(@unlink($accLinkName)) return TRUE; + else return PEAR::raiseError( + "RawMediaData::release: symlink unlink failed", GBERR_FILEIO + ); + } + /** + * Delete media file from filesystem + * + * @return boolean or PEAR::error + */ + function delete() + { + if(!$this->exists) return FALSE; + if(@unlink($this->fname)){ + $this->exists = FALSE; + return TRUE; + }else{ + return PEAR::raiseError( + "RawMediaData::delete: unlink failed ({$this->fname})", + GBERR_FILEIO + ); + } + return $this->exists; + } + + /** + * Analyze media file with getid3 module + * + * @return hierarchical hasharray with information about media file + */ + function analyze() + { + if(!$this->exists) return FALSE; + $ia = GetAllFileinfo($this->fname); + return $ia; + } + + /** + * Get mime-type returned by getid3 module + * + * @return string + */ + function getMime() + { + $a = $this->analyze(); + if($a === FALSE) return $a; + return $a['mime_type']; + } + + /** + * Contruct filepath of media file + * + * @return string + */ + function makeFname() + { + return "{$this->resDir}/{$this->gunid}"; + } + /** + * Test method + * + * @param testFname1 string + * @param testFname2 string + * @param accLinkFname string + * @return string + */ + function test($testFname1, $testFname2, $accLinkFname) + { + $log = ''; + if($this->exists()) + $log .= "---: exists: YES\n"; + else + $log .= "---: exists: NO\n"; + if(!($r = $this->delete())) + $log .= "---: delete: nothing to delete\n"; + if(PEAR::isError($r)) + $log .= "ERR: ".$r->getMessage()."\n"; + if($r = $this->insert($testFname1)) + $log .= "---: insert: already exists\n"; + if(PEAR::isError($r)) + $log .= "ERR: ".$r->getMessage()."\n"; + if($r = $this->replace($testFname2)) + $log .= "---: replace: already exists\n"; + if(PEAR::isError($r)) + $log .= "ERR: ".$r->getMessage()."\n"; + if($this->exists()) + $log .= "---: exists: YES\n"; + else + $log .= "---: exists: NO\n"; + if(!$this->access($accLinkFname)) + $log .= "---: access: not exists\n"; + if(($ft = filetype($accLinkFname)) == 'link'){ + if(($rl = readlink($accLinkFname)) != $this->fname) + $log .= "ERR: wrong target ($rl)\n"; + }else + $log .= "ERR: wrong file type ($ft)\n"; + if(!$this->release($accLinkFname)) + $log .= "---: access: not exists\n"; + return $log; + } +} +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/StoredFile.php b/livesupport/modules/storageServer/var/StoredFile.php new file mode 100644 index 000000000..221d6afa3 --- /dev/null +++ b/livesupport/modules/storageServer/var/StoredFile.php @@ -0,0 +1,504 @@ + + * Represents one virtual file in storage. Virtual file has up to two parts: + *
    + *
  • metada in database - represeted by MetaData class
  • + *
  • binary media data in real file + * - represented by RawMediaData class
  • + *
+ * @see GreenBox + * @see MetaData + * @see RawMediaData + */ +class StoredFile{ + /* ========================================================== constructor */ + /** + * Constructor, but shouldn't be externally called + * + * @param gb reference to GreenBox object + * @param gunid string, optional, globally unique id of file + * @return this + */ + function StoredFile(&$gb, $gunid=NULL) + { + $this->gb =& $gb; + $this->dbc =& $gb->dbc; + $this->filesTable = $gb->filesTable; + $this->accessTable= $gb->accessTable; + $this->gunid = $gunid; + if(is_null($this->gunid)) $this->gunid = $this->_createGunid(); + $this->resDir = $this->_getResDir($this->gunid); + $this->accessDir = $this->gb->accessDir; + $this->rmd =& new RawMediaData($this->gunid, $this->resDir); + $this->md =& new MetaData(&$gb, $this->gunid); + return $this->gunid; + } + /* ========= 'factory' methods - should be called to construct StoredFile */ + /** + * Create instace of StoreFile object and insert new file + * + * @param gb reference to GreenBox object + * @param oid int, local object id in the tree + * @param name string, name of new file + * @param mediaFileLP string, local path to media file + * @param mdataFileLP string, local path to metadata XML file + * @return instace of StoredFile object + */ + function insert(&$gb, $oid, $name, $mediaFileLP='', $mdataFileLP='') + { + $ac =& new StoredFile(&$gb); + $ac->name = $name; + $ac->id = $oid; + $ac->type = "unKnown"; + if($ac->name=='') $ac->name=$ac->gunid; + $this->dbc->query("BEGIN"); + $res = $ac->dbc->query("INSERT INTO {$ac->filesTable} + (id, name, gunid, type) + VALUES + ('$oid', '{$ac->name}', '{$ac->gunid}', '{$ac->type}')" + ); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + if($mdataFileLP != ''){ + $res = $ac->md->insert($mdataFileLP); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + } + if($mediaFileLP != ''){ + $res = $ac->rmd->insert($mediaFileLP); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + $mime = $ac->rmd->getMime(); +// return PEAR::raiseError("X1"); +// $gb->debugLog("gunid={$ac->gunid}, mime=$mime"); + if($mime !== FALSE){ + $res = $ac->dbc->query("UPDATE {$ac->filesTable} + SET type='$mime' WHERE id='$oid'"); + if(PEAR::isError($res)){ + $ac->dbc->query("ROLLBACK"); return $res; + } + } + } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + return $ac; + } + /** + * Create instace of StoreFile object and recall existing file.
+ * Should be supplied oid XOR gunid - not both ;) + * + * @param gb reference to GreenBox object + * @param oid int, optional, local object id in the tree + * @param gunid string, optional, global unique id of file + * @return instace of StoredFile object + */ + function recall(&$gb, $oid='', $gunid='') + { + $cond = ($gunid=='' ? "id='$oid'" : "gunid='$gunid'" ); + $row = $gb->dbc->getRow("SELECT id, gunid, type, name + FROM {$gb->filesTable} WHERE $cond"); + if(PEAR::isError($row)) return $row; + if(is_null($row)){ + return PEAR::raiseError( + "StoredFile::recall: fileobj not exist", GBERR_FOBJNEX); + } + $ac =& new StoredFile(&$gb, $row['gunid']); + $ac->type = $row['type']; + $ac->name = $row['name']; + $ac->id = $row['id']; + return $ac; + } + /** + * Create instace of StoreFile object and recall existing file from tmpLink + * + * @param gb reference to GreenBox object + * @param tmpLink string + * @param sessid string + */ + function recallFromLink(&$gb, $tmpLink, $sessid) + { + $gunid = $gb->dbc->getOne("SELECT gunid FROM {$gb->accessTable} + WHERE tmpLink='$tmpLink' AND sessid='$sessid'"); + if(PEAR::isError($gunid)) return $gunid; + if(is_null($gunid)) return PEAR::raiseError( + "StoredFile::recallFromLink: accessobj not exist", GBERR_AOBJNEX); + return StoredFile::recall(&$gb, '', $gunid); + } + /** + * Create instace of StoreFile object and make copy of existing file + * + * @param src reference to source object + * @param nid int, new local id + */ + function copyOf(&$src, $nid) + { + $ac =& StoredFile::insert(&$src->gb, $nid, $src->name, $src->_getRealRADFname(), ''); + if(PEAR::isError($ac)) return $ac; + $ac->md->replace($src->md->getMetaData(), 'xml'); + return $ac; + } + + /* ======================================================= public methods */ + /** + * Replace existing file with new data + * + * @param oid int, local id + * @param name string, name of file + * @param mediaFileLP string, local path to media file + * @param mdataFileLP string, local path to metadata XML file or XML string + * @param mdataLoc string 'file'|'string' + */ + function replace($oid, $name, $mediaFileLP='', $mdataFileLP='', + $mdataLoc='file') + { + $this->dbc->query("BEGIN"); + $res = $this->dbc->query("UPDATE {$this->filesTable} + SET name='$name', type='{$this->type}' WHERE id='$oid'"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + if($mediaFileLP != ''){ + $res = $this->rmd->replace($mediaFileLP, $this->type); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + $mime = $this->rmd->getMime(); + if($mime !== FALSE){ + $res = $this->dbc->query("UPDATE {$this->filesTable} + SET type='$mime' WHERE id='$oid'"); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + } + } + if($mdataFileLP != ''){ + $res = $this->md->replace($mdataFileLP, $mdataLoc); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)){ + $this->dbc->query("ROLLBACK"); return $res; + } + return TRUE; + } + /** + * Increase access counter, create access record, + * call access method of RawMediaData + * + * @param sessid string + */ + function accessRawMediaData($sessid) + { + $this->dbc->query("BEGIN"); + $res = $this->dbc->query("UPDATE {$this->filesTable} + SET currentlyaccessing=currentlyaccessing+1 + WHERE gunid='{$this->gunid}'"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $accLinkName = $this->_getAccessFname($sessid, $this->_getExt()); + $res = $this->dbc->query("INSERT INTO {$this->accessTable} + (gunid, sessid, tmplink, ts) + VALUES + ('{$this->gunid}', '$sessid', '$accLinkName', now())"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $acc = $this->rmd->access($accLinkName); + if(PEAR::isError($acc)){ $this->dbc->query("ROLLBACK"); return $acc; } + if($acc === FALSE){ + $this->dbc->query("ROLLBACK"); + return PEAR::raiseError( + 'StoredFile::accessRawMediaData: not exists' + ); + } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + return $acc; + } + /** + * Decrease access couter, delete access record, + * call release method of RawMediaData + * + * @param sessid string + */ + function releaseRawMediaData($sessid) + { + $this->dbc->query("BEGIN"); + $res = $this->dbc->query("UPDATE {$this->filesTable} + SET currentlyaccessing=currentlyaccessing-1 + WHERE gunid='{$this->gunid}' AND currentlyaccessing>0" + ); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $ca = $this->dbc->getOne("SELECT currentlyaccessing + FROM {$this->filesTable} + WHERE gunid='{$this->gunid}'" + ); + if(PEAR::isError($ca)){ $this->dbc->query("ROLLBACK"); return $ca; } + $accLinkName = $this->_getAccessFname($sessid, $this->_getExt()); + $res = $this->dbc->query("DELETE FROM {$this->accessTable} + WHERE gunid='{$this->gunid}' AND sessid='$sessid' + AND tmplink='$accLinkName'"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + if(intval($ca)==0) return $this->rmd->release($accLinkName); + return TRUE; + } + /** + * Replace metadata with new XML file + * + * @param mdataFileLP string, local path to metadata XML file or XML string + * @param mdataLoc string 'file'|'string' + */ + function replaceMetaData($mdataFileLP, $mdataLoc='file') + { + $this->dbc->query("BEGIN"); + $res = $this->md->replace($mdataFileLP, $mdataLoc); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)) return $res; + return TRUE; + } + /** + * Update metadata with new XML file + * + * @param mdataFileLP string, local path to metadata XML file or XML string + * @param mdataLoc string 'file'|'string' + * @return boolean or PEAR::error + */ + function updateMetaData($mdataFileLP, $mdataLoc='file') + { + $this->dbc->query("BEGIN"); + $res = $this->md->update($mdataFileLP, $mdataLoc); + if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; } + $res = $this->dbc->query("COMMIT"); + if(PEAR::isError($res)) return $res; + return TRUE; + } + /** + * Update object namespace and value of one metadata record + * + * @param mdid int, metadata record id + * @param object string, object value, e.g. title string + * @param objns string, object namespace prefix, have to be defined + * in file's metadata (or reserved prefix) + * @see MetaData + * @return boolean or PEAR::error + */ + function updateMetaDataRecord($mdid, $object, $objns='_L') + { + return $this->md->updateRecord($mdid, $object, $objns='_L'); + } + /** + * Get metadata as XML string + * + * @return XML string + * @see MetaData + */ + function getMetaData() + { + return $this->md->getMetaData(); + } + /** + * Analyze file with getid3 module.
+ * Obtain some metadata stored in media file.
+ * This method should be used for prefilling metadata input form. + * + * @return array + * @see MetaData + */ + function analyzeMediaFile() + { + $ia = $this->rmd->analyze(); + return $ia; + } + /** + * Rename stored virtual file + * + * @param newname string + * @return true or PEAR::error + */ + function rename($newname) + { + $res = $this->dbc->query("UPDATE {$this->filesTable} SET name='$newname' + WHERE gunid='{$this->gunid}'"); + if(PEAR::isError($res)) return $res; + return TRUE; + } + /** + * Delete stored virtual file + * + * @see RawMediaData + * @see MetaData + */ + function delete() + { + $res = $this->rmd->delete(); + if(PEAR::isError($res)) return $res; + $res = $this->md->delete(); + if(PEAR::isError($res)) return $res; + $links = $this->dbc->getAll("SELECT tmplink FROM {$this->accessTable} + WHERE gunid='{$this->gunid}'"); + if(is_array($links)) foreach($links as $i=>$item){ + @unlink($item['tmplink']); + } + $res = $this->dbc->query("DELETE FROM {$this->accessTable} + WHERE gunid='{$this->gunid}'"); + if(PEAR::isError($res)) return $res; + $res = $this->dbc->query("DELETE FROM {$this->filesTable} + WHERE gunid='{$this->gunid}'"); + if(PEAR::isError($res)) return $res; + return TRUE; + } + + /** + * Returns true if virtual file is accessed.
+ * Static or dynamic call is possible. + * + * @param gunid string, optional (for static call), global unique id + */ + function isAccessed($gunid=NULL) + { + if(is_null($gunid)) $gunid = $this->gunid; + return (0 < $this->dbc->getOne(" + SELECT currentlyAccessing FROM {$this->filesTable} + WHERE gunid='$gunid' + ")); + } + /** + * Returns local id of virtual file + * + */ + function getId() + { + return $this->id; + } + /** + * Returns true if raw media file exists + * + */ + function exists() + { + $indb = $this->dbc->getRow("SELECT gunid FROM {$this->filesTable} + WHERE gunid='{$this->gunid}'"); + return (!is_null($indb) && $this->rmd->exists()); + } + + /* ==================================================== "private" methods */ + /** + * Create new global unique id + * + */ + function _createGunid() + { + return md5(microtime().$_SERVER['SERVER_ADDR'].rand(). + "org.mdlf.livesupport"); + } + /** + * Get local id from global id. + * Static or dynamic call is possible. + * + * @param gunid string, optional (for static call), + * global unique id of file + */ + function _idFromGunid($gunid=NULL) + { + if(is_null($gunid)) $gunid = $this->$gunid; + $id = $this->dbc->getOne("SELECT id FROM {$this->filesTable} + WHERE gunid='$gunid'"); + if(is_null($id)) return PEAR::raiseError( + "StoredFile::_idFromGunid: no such global unique id ($gunid)" + ); + return $id; + } + /** + * Return suitable extension.
+ * TODO: make it general - is any tool for it? + * + * @return string file extension without a dot + */ + function _getExt() + { + switch($this->type){ + case"audio/mpeg": $ext="mp3"; break; + case"audio/x-wave": $ext="wav"; break; + case"application/x-ogg": $ext="ogg"; break; + default: $ext="bin"; break; + } + return $ext; + } + /** + * Get filetype from global id + * + * @param gunid string, optional, global unique id of file + */ + function _getType($gunid) + { + return $this->dbc->getOne("SELECT type FROM {$this->filesTable} + WHERE gunid='$gunid'"); + } + /** + * Get and optionaly create subdirectory in real filesystem for storing + * raw media data + * + */ + function _getResDir() + { + $resDir="{$this->gb->storageDir}/".substr($this->gunid, 0, 3); + if(!file_exists($resDir)){ mkdir($resDir, 02775); } + return $resDir; + } + /** + * Get real filename of raw media data + * + * @see RawMediaData + */ + function _getRealRADFname() + { + return $this->rmd->getFname(); + } + /** + * Create and return name for temporary symlink.
+ * TODO: Should be more unique + * + */ + function _getAccessFname($sessid, $ext='EXT') + { + $spart = md5("{$sessid}_{$this->gunid}"); + return "{$this->accessDir}/$spart.$ext"; + } +} +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/conf.php b/livesupport/modules/storageServer/var/conf.php new file mode 100644 index 000000000..eb407008b --- /dev/null +++ b/livesupport/modules/storageServer/var/conf.php @@ -0,0 +1,63 @@ + array( + 'username' => 'test', + 'password' => 'test', + 'hostspec' => 'localhost', +# 'hostspec' => '127.0.0.1', // for bad resolver + 'phptype' => 'pgsql', + 'database' => 'LiveSupport-test', + ), + 'tblNamePrefix' => 'gb_', + 'authCookieName'=> 'gbsid', + 'RootNode' => 'RootNode', + 'tmpRootPass' => 'q', + 'objtypes' => array( + 'RootNode' => array('Folder'), + 'Storage' => array('Folder', 'File', 'Replica'), + 'Folder' => array('Folder', 'File', 'Replica'), + 'File' => array(), + 'Replica' => array(), + ), + 'allowedActions'=> array( + 'RootNode' => array('classes', 'subjects'), + 'Folder' => array('editPrivs', 'write', 'read'), + 'File' => array('editPrivs', 'write', 'read'), + 'Replica' => array('editPrivs', 'write', 'read'), + '_class' => array('editPrivs', 'write', 'read'), + ), + 'allActions' => array( + 'editPrivs', 'write', 'read', 'classes', 'subjects' + ), + 'storageDir' => dirname(getcwd()).'/stor', + 'accessDir' => dirname(getcwd()).'/access', +); +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/default.css b/livesupport/modules/storageServer/var/html/default.css new file mode 100644 index 000000000..a065ef231 --- /dev/null +++ b/livesupport/modules/storageServer/var/html/default.css @@ -0,0 +1,19 @@ + diff --git a/livesupport/modules/storageServer/var/html/gbHtmlBrowse.php b/livesupport/modules/storageServer/var/html/gbHtmlBrowse.php new file mode 100644 index 000000000..342149bd1 --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlBrowse.php @@ -0,0 +1,245 @@ +\n"; print_r($_FILES); print_r($_REQUEST); print_r($_SERVER); exit; +require_once"gbHtml_h.php"; +require_once"gbHtmlTestAuth.php"; + +$fldsname=array('author'=>'Author', 'title'=>'Title', 'creator'=>'Creator', + 'description'=>'Description', 'subject'=>'Subject', 'genre'=>'Genre'); + +$sessid = $_REQUEST[$config['authCookieName']]; +$userid = $gb->getSessUserId($sessid); +$login = $gb->getSessLogin($sessid); + +#$path = ($_REQUEST['path']=='' ? '/' : $_REQUEST['path']); +$id = (!$_REQUEST['id'] ? $gb->storId : $_REQUEST['id']); + +#echo"
\nsessid=$sessid\nuserid=$userid\nlogin=$login\n"; exit;
+
+$tpldata = array(
+    'msg'       => $_SESSION['alertMsg'],
+    'loggedAs'  => $login,
+    'id'        => $id,
+); unset($_SESSION['alertMsg']);
+
+switch($_REQUEST['act']){
+    case"getHomeDir":
+        $id = $gb->getObjId($login, $gb->storId);
+        $tpldata['id'] = $id;
+    default:
+#        echo"
\n$path\n$upath
\n"; print_r($_FILES); print_r($_REQUEST); exit; + $tpldata=array_merge($tpldata, array( + 'pathdata' => $gb->getPath($id, $sessid), + 'listdata' => ($gb->getObjType($id)=='Folder'? + $gb->listFolder($id, $sessid):array() + ), + 'tree' => ($_REQUEST['tree']=='Y'), + 'showPath' => true, + 'showTree' => true, + )); + if($_REQUEST['tree']=='Y'){ + $tpldata['treedata'] = $gb->getSubTree($id, $sessid); + } + break; + case"newfile": + $tpldata=array( + 'pathdata' => $gb->getPath($id, $sessid), + 'showEdit' => true, + 'id' => $id, + ); + break; + case"sform": + $tpldata=array( +# 'pathdata' => $gb->getPath($path, $sessid), + 'showSForm' => true, + 'id' => $id, + ); + break; + case"search": + $tpldata=array( +# 'pathdata' => $gb->getPath($path, $sessid), + 'search' => $gb->localSearch($_REQUEST['srch'], $sessid), + 'showSRes' => true, + 'id' => $id, + ); + break; +} + +if(PEAR::isError($tpldata['listdata'])){ + $tpldata['msg'] = $tpldata['listdata']->getMessage(); + $tpldata['listdata'] = array(); +} +#echo"
\n$path
\n"; print_r($tpldata['pathdata']); print_r($tpldata); exit; + +$tpldata['showMenu']=true; + + +// =================== template: =================== + +?> + +Browser + + + + + + + + +

+ Home directory + Upload new file + Create new folder + Search +

+ + + +

+ Tree   + + + / + : + + permissions + +

+ + + + + + +
+ + + + + + + + + +
+ href="gbHtmlBrowse.php?id="> + + 'D', 'File'=>'F', 'Replica'=>'R'); echo$a[$o['type']]?> +  rename + +  move +  copy +  replicate + +  permissions +  DEL + +  Access +  Analyze +  MetaData + + +   (->) + +
No objects
+ + + +
+?> + + + + + + + + +
File name:
Media file:
Metadata file:
+ +
+ + + +
+ + + +
+ + + +
Search string:
+ + +
+ + + +
    +$v){?> +
  • + + No items found + +
+ + + + + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHtmlLogin.php b/livesupport/modules/storageServer/var/html/gbHtmlLogin.php new file mode 100644 index 000000000..fe6597c4f --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlLogin.php @@ -0,0 +1,73 @@ + $gb->getSubjects(), + 'actions' => $gb->getAllActions(), + 'objects' => $gb->getAllObjects(), + 'msg' => $_SESSION['alertMsg'] +); +unset($_SESSION['alertMsg']); + +// forms prefill: +if(is_array($_SESSION['lastPost'])) $d = array_merge($d, array( + 'lastSubj' => $_SESSION['lastPost']['subj'], + 'lastAction'=> $_SESSION['lastPost']['permAction'], + 'lastObj' => $_SESSION['lastPost']['obj'] +)); +unset($_SESSION['lastPost']); + +#header("Content-type: text/plain"); print_r($d); exit; +#require_once"gbHtml_f.php"; +// template follows: +?> + +Storage - login + + + + + +
+ Test accounts/pass: + +
+ +

Storage - login

+ +
+ + + + +
Login:
Password:
+ +
+
+ + + + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHtmlPerms.php b/livesupport/modules/storageServer/var/html/gbHtmlPerms.php new file mode 100644 index 000000000..95e43abfc --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlPerms.php @@ -0,0 +1,111 @@ +getSessUserId($_REQUEST[$config['authCookieName']]); +$login = $gb->getSessLogin($_REQUEST[$config['authCookieName']]); + +$id = (!$_REQUEST['id'] ? $gb->storId : $_REQUEST['id']); + +#header("Content-type: text/plain"); print_r($_REQUEST); exit; +#header("Content-type: text/plain"); echo $gb->dumpTree($id, ' ')."\n"; exit; + +// prefill data structure for template +$tpldata = array( + 'pathdata' => $gb->getPath($id), + 'perms' => $gb->getObjPerms($id), + 'actions' => $gb->getAllowedActions($gb->getObjType($id)), + 'subjects' => $gb->getSubjects(), + 'id' => $id, + 'loggedAs' => $login, +); +$tpldata['msg'] = $_SESSION['alertMsg']; unset($_SESSION['alertMsg']); + +#header("Content-type: text/plain"); print_r($tpldata); exit; + + +#require_once"gbHtml_f.php"; +// template follows: +?> + +Storage - permission editor + + + + + + +

Permission editor

+ +

Path: + $it) {?> + / + + +

+ + + +0) foreach($tpldata['perms'] as $k=>$row) { + $da=($row['type']=='A' ? 'allow' : ($row['type']=='D' ? 'deny' : $row['type']));?> + + + + + + + + + +
subject nameactionpermission
> + remove +
none
+ +
+Add permission + +for action + +to subject + + + + +
+ + + + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHtmlSubj.php b/livesupport/modules/storageServer/var/html/gbHtmlSubj.php new file mode 100644 index 000000000..1dca46d21 --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlSubj.php @@ -0,0 +1,162 @@ +listGroup($id)); exit; + +// prefill data structure for template +switch($type){ + case "list": + $d = array( + 'subj' => $gb->getSubjectsWCnt(), + 'loggedAs' => $login + ); + break; + case "group": + $d = array( + 'rows' => $gb->listGroup($id), + 'id' => $id, + 'loggedAs' => $login, + 'gname' => $gb->getSubjName($id), + 'subj' => $gb->getSubjects() + ); + break; + case "passwd": + break; + default: +} +$d['msg'] = $_SESSION['alertMsg']; unset($_SESSION['alertMsg']); + +#header("Content-type: text/plain"); print_r($d); echo($list ? 'Y' : 'N')."\n"; exit; +#require_once"gbHtml_f.php"; +// template follows: +?> + +Storage - user and roles editor + + + + + + +

User/Group editor

+ + +

Subjects:

+ + +0) foreach($d['subj'] as $k=>$c) {?> + + + (G:) (U) + + + + + +
idloginuser/group
+ + + + + + remove + change password +
none
+ +
+Add subject with name: +[and password: ] + + +
+ + + +

Subjects in group :

+ + + + + +0) foreach($d['rows'] as $k=>$row) {?> + + + (G) (U) + + + + + +
+ All subjects +
+ + + + + + + removeFromGroup + +
none
+ +
+Add subject + +to group + + + + +
+ + +
+ + + + + +
Old password:
New password:
Retype:
+ + +
+ + + + + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHtmlTestAuth.php b/livesupport/modules/storageServer/var/html/gbHtmlTestAuth.php new file mode 100644 index 000000000..f8f9f9dca --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlTestAuth.php @@ -0,0 +1,9 @@ +getSessLogin($_REQUEST[$config['authCookieName']]); +if(!isset($login)||$login==''){ + $_SESSION['alertMsg'] = "Login required"; + header("Location: gbHtmlLogin.php"); + exit; +} +?> diff --git a/livesupport/modules/storageServer/var/html/gbHtmlTestData.php b/livesupport/modules/storageServer/var/html/gbHtmlTestData.php new file mode 100644 index 000000000..66ab8e3dd --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtmlTestData.php @@ -0,0 +1,13 @@ +initDb(); +$gb->init(); +#system("rm -f {$config['storageDir']}/*.bin {$config['storageDir']}/*.xml"); +$d = $gb->testData(); +$gb->putFile('/folder1/folder1_2/folder1_2_1', 'fileA', "123\n345\n", "", 'at'); +$gb->createReplica('/folder1/folder1_2/folder1_2_1/fileA', '/folder1/folder1_2/folder1_2_1', 'replFA', 'at'); +$gb->putFile('/folder1/folder1_2/folder1_2_1', 'fileB', "123\n345\n789\n", "", 'at'); +$gb->ovewriteMetadata('/folder1/folder1_2/folder1_2_1/fileA', "\n", 'at'); + +$gb->deleteFile('/folder1/folder1_2/folder1_2_1/fileB', 'at'); + +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHtml_h.php b/livesupport/modules/storageServer/var/html/gbHtml_h.php new file mode 100644 index 000000000..0648fe2c7 --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHtml_h.php @@ -0,0 +1,25 @@ +gm:\n".$err->getMessage()."\ndi:\n".$err->getDebugInfo()."\nui:\n".$err->getUserInfo()."\n"; + echo "
BackTrace:\n"; + print_r($err->backtrace); + echo "
\n"; + exit; +} + +$dbc = DB::connect($config['dsn'], TRUE); +$dbc->setFetchMode(DB_FETCHMODE_ASSOC); +$gb = &new GreenBox(&$dbc, $config); + +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/html/gbHttp.php b/livesupport/modules/storageServer/var/html/gbHttp.php new file mode 100644 index 000000000..278f24e66 --- /dev/null +++ b/livesupport/modules/storageServer/var/html/gbHttp.php @@ -0,0 +1,197 @@ +\n"; print_r($_SERVER); exit; + +define('BROWSER', "gbHtmlBrowse.php"); + + +$sessid = $_REQUEST[$config['authCookieName']]; +$userid = $gb->getSessUserId($sessid); +$login = $gb->getSessLogin($sessid); + +#$path = ($_REQUEST['path']=='' ? '/' : $_REQUEST['path']); +#$upath = urlencode($path); +#$id = $gb->_idFromPath($path); +$id = (!$_REQUEST['id'] ? $gb->storId : $_REQUEST['id']); + + +#if(PEAR::isError($id)){ $_SESSION['msg'] = $id->getMessage(); header("Location: ".BROWSER."?id=$id"); exit; } +$redirUrl="gbHtmlBrowse.php?id=$id"; + +switch($_REQUEST['act']){ +// --- authentication --- + case"login"; +# echo"
\n"; print_r($_REQUEST); exit;
+        $sessid = $gb->login($_REQUEST['login'], $_REQUEST['pass']);
+        if($sessid && !PEAR::isError($sessid)){
+#            echo"
$sessid\n"; print_r($_REQUEST); exit;
+            setcookie($config['authCookieName'], $sessid);
+            $redirUrl="gbHtmlBrowse.php";
+            $fid = $gb->getObjId($_REQUEST['login'], $gb->storId);
+            if(!PEAR::isError($fid)) $redirUrl.="?id=$fid";
+        }else{ $redirUrl="gbHtmlLogin.php"; $_SESSION['alertMsg']='Login failed.'; }
+#        echo"
$redirUrl\n"; print_r($_REQUEST); exit;
+    break;
+    case"logout";
+        $gb->logout($sessid);
+        setcookie($config['authCookieName'], '');
+        $redirUrl="gbHtmlLogin.php";
+    break;
+
+// --- files ---
+    case"upload":
+        $tmpgunid = md5(microtime().$_SERVER['SERVER_ADDR'].rand()."org.mdlf.livesupport");
+        $ntmp = "{$gb->storageDir}/buffer/$tmpgunid";
+#        $ntmp = tempnam(""{$gb->storageDir}/buffer", 'gbTmp_');
+        $mdtmp = "";
+        move_uploaded_file($_FILES['mediafile']['tmp_name'], $ntmp); chmod($ntmp, 0664);
+        if($_FILES['mdatafile']['tmp_name']){
+            $mdtmp = "$ntmp.xml";
+            if(move_uploaded_file($_FILES['mdatafile']['tmp_name'], $mdtmp)){
+                chmod($mdtmp, 0664);
+            }
+        }
+        $r = $gb->putFile($id, $_REQUEST['filename'], $ntmp, $mdtmp, $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        else{
+#            $gb->updateMetadataDB($gb->_pathFromId($r), $mdata, $sessid);
+            @unlink($ntmp);
+            @unlink($mdtmp);
+        }
+        $redirUrl = BROWSER."?id=$id";
+    break;
+    case"newFolder":
+        $r = $gb->createFolder($id, $_REQUEST['newname'], $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        $redirUrl = BROWSER."?id=$id";
+    break;
+    case"rename":
+        $parid = $gb->getparent($id);
+        $r = $gb->renameFile($id, $_REQUEST['newname'], $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        $redirUrl = BROWSER."?id=$parid";
+    break;
+/* NOT WORKING - sorry */
+    case"move":
+        $newPath = urlencode($_REQUEST['newPath']);
+        $did = $gb->getObjIdFromRelPath($id, $newPath);
+        $parid = $gb->getparent($id);
+        $r = $gb->moveFile($id, $did, $sessid);
+        if(PEAR::isError($r)){
+            $_SESSION['alertMsg'] = $r->getMessage();
+            $redirUrl = BROWSER."?id=$parid";
+        }
+        else $redirUrl = BROWSER."?id=$did";
+    break;
+    case"copy":
+        $newPath = urldecode($_REQUEST['newPath']);
+        $did = $gb->getObjIdFromRelPath($id, $newPath);
+        $parid = $gb->getparent($id);
+#        echo"
\n$id\t$newPath\t$did\n"; print_r($did); exit;
+        $r = $gb->copyFile($id, $did, $sessid);
+        if(PEAR::isError($r)){
+            $_SESSION['alertMsg'] = $r->getMessage();
+            $redirUrl = BROWSER."?id=$parid";
+        }
+        else $redirUrl = BROWSER."?id=$did";
+    break;
+    case"repl":
+        $unewpath = urlencode($_REQUEST['newpath']);
+        $r = $gb->createReplica($id, $_REQUEST['newpath'], '', $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        $redirUrl = BROWSER."?id=$newparid";
+    break;
+/* */
+    case"delete":
+        $parid = $gb->getparent($id);
+        $r = $gb->deleteFile($id, $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        $redirUrl = BROWSER."?id=$parid";
+    break;
+    case"getFile":
+#        echo"
$t, $ctype\n"; exit;
+#        $r = $gb->getFile($id, $sessid);
+        $r = $gb->access($id, $sessid);
+        if(PEAR::isError($r)) $_SESSION['alertMsg'] = $r->getMessage();
+        else echo $r;
+        exit;
+    break;
+    case"getMdata":
+        header("Content-type: text/xml");
+        $r = $gb->getMdata($id, $sessid);
+        print_r($r);
+        exit;
+    break;
+    case"getInfo":
+        header("Content-type: text/plain");
+        $ia = $gb->analyzeFile($id, $sessid);
+        echo"fileformat: {$ia['fileformat']}\n";
+        echo"channels: {$ia['audio']['channels']}\n";
+        echo"sample_rate: {$ia['audio']['sample_rate']}\n";
+        echo"bits_per_sample: {$ia['audio']['bits_per_sample']}\n";
+        echo"channelmode: {$ia['audio']['channelmode']}\n";
+        echo"title: {$ia['id3v1']['title']}\n";
+        echo"artist: {$ia['id3v1']['artist']}\n";
+        echo"comment: {$ia['id3v1']['comment']}\n";
+#        echo": {$ia['id3v1']['']}\n";
+#        print_r($ia);
+        exit;
+    break;
+
+// --- subjs ----
+    case"addSubj";
+        $redirUrl="gbHtmlSubj.php";
+        if($gb->checkPerm($userid, 'subjects'))
+            $res = $gb->addSubj($_REQUEST['login'], ($_REQUEST['pass']=='' ? NULL:$_REQUEST['pass'] ));
+        else{ $_SESSION['alertMsg']='Access denied.'; break; }
+        if(PEAR::isError($res)) $_SESSION['alertMsg'] = $res->getMessage();
+    break;
+    case"removeSubj";
+        $redirUrl="gbHtmlSubj.php";
+        if($gb->checkPerm($userid, 'subjects'))
+            $res = $gb->removeSubj($_REQUEST['login']);
+        else{ $_SESSION['alertMsg']='Access denied.'; break; }
+        if(PEAR::isError($res)) $_SESSION['alertMsg'] = $res->getMessage();
+    break;
+    case"passwd";
+        $redirUrl="gbHtmlSubj.php";
+        $ulogin = $gb->getSubjName($_REQUEST['uid']);
+        if($userid != $_REQUEST['uid'] &&
+            ! $gb->checkPerm($userid, 'subjects')){
+            $_SESSION['alertMsg']='Access denied..';
+            break;
+        }
+        if(FALSE === $gb->authenticate($ulogin, $_REQUEST['oldpass'])){
+            $_SESSION['alertMsg']='Wrong old pasword.';
+            break;
+        }
+        if($_REQUEST['pass'] !== $_REQUEST['pass2']){
+            $_SESSION['alertMsg']="Passwords do not match. ({$_REQUEST['pass']}/{$_REQUEST['pass2']})";
+            break;
+        }
+        $gb->passwd($ulogin, $_REQUEST['oldpass'], $_REQUEST['pass']);
+    break;
+
+// --- perms ---
+    case"addPerm";
+        if($gb->checkPerm($userid, 'editPerms', $_REQUEST['id']))
+            $gb->addPerm($_REQUEST['subj'], $_REQUEST['permAction'], $_REQUEST['id'], $_REQUEST['allowDeny']);
+        else $_SESSION['alertMsg']='Access denied.';
+        $redirUrl="gbHtmlPerms.php?id=$id";
+    break;
+    case"removePerm";
+        if($gb->checkPerm($userid, 'editPerms', $_REQUEST['oid']))
+            $gb->removePerm($_GET['permid']);
+        else $_SESSION['alertMsg']='Access denied.';
+        $redirUrl="gbHtmlPerms.php?id=$id";
+    break;
+
+    default:
+        $_SESSION['alertMsg']="Unknown method: {$_REQUEST['act']}";
+        $redirUrl="gbHtmlLogin.php";
+}
+
+#echo"
$redirUrl\n"; print_r($_REQUEST); exit;
+header("Location: $redirUrl");
+?>
\ No newline at end of file
diff --git a/livesupport/modules/storageServer/var/html/index.php b/livesupport/modules/storageServer/var/html/index.php
new file mode 100644
index 000000000..33f221d4f
--- /dev/null
+++ b/livesupport/modules/storageServer/var/html/index.php
@@ -0,0 +1,34 @@
+
diff --git a/livesupport/modules/storageServer/var/index.php b/livesupport/modules/storageServer/var/index.php
new file mode 100644
index 000000000..7ef1341ad
--- /dev/null
+++ b/livesupport/modules/storageServer/var/index.php
@@ -0,0 +1,45 @@
+
+
+Storage module
+
+

Storage module

+
+HTML client
+XmlRpc test
+Test
+ + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/install/index.php b/livesupport/modules/storageServer/var/install/index.php new file mode 100644 index 000000000..e0f92e930 --- /dev/null +++ b/livesupport/modules/storageServer/var/install/index.php @@ -0,0 +1,34 @@ + diff --git a/livesupport/modules/storageServer/var/install/install.php b/livesupport/modules/storageServer/var/install/install.php new file mode 100644 index 000000000..ad05b81f1 --- /dev/null +++ b/livesupport/modules/storageServer/var/install/install.php @@ -0,0 +1,87 @@ +getMessage()."\ndi:\n".$err->getDebugInfo()."\nui:\n".$err->getUserInfo()."\n
\n"; + exit; +} + + +PEAR::setErrorHandling(PEAR_ERROR_PRINT, "%s
\n"); +$dbc = DB::connect($config['dsn'], TRUE); +if(PEAR::isError($dbc)){ + echo "Database connection problem.\n"; + echo "Check if database '{$config['dsn']['database']}' exists with corresponding permissions.\n"; + echo "Database access is defined by 'dsn' values in conf.php.\n"; + exit; +} + +$dbc->setFetchMode(DB_FETCHMODE_ASSOC); +$gb = &new GreenBox(&$dbc, $config); + +echo "Storage: Install ...\n"; +$dbc->setErrorHandling(PEAR_ERROR_RETURN); +$gb->uninstall(); +PEAR::setErrorHandling(PEAR_ERROR_PRINT, "%s
\n"); +$gb->install(); + +echo " Testing ...\n"; +$gb->test(); +$log = $gb->test_log; +echo " TESTS:\n$log\n---\n"; + +#echo " Reinstall + testdata insert ...\n"; +#$gb->reinstall(); +#$gb->sessid = $gb->login('root', $gb->config['tmpRootPass']); +#$gb->testData(); +#$gb->logout($gb->sessid); unset($gb->sessid); + +echo " TREE DUMP:\n"; +echo $gb->dumpTree(); + +echo " Delete test data ...\n"; +$gb->deleteData(); + +if(!($fp = @fopen($config['storageDir']."/_writeTest", 'w'))) + echo "\n!!! make {$config['storageDir']} dir webdaemon-writeable !!!\nand run install again\n\n"; +else{ + fclose($fp); unlink($config['storageDir']."/_writeTest"); + echo "\nStorage is probably installed OK\n"; +} + +$dbc->disconnect(); +?> diff --git a/livesupport/modules/storageServer/var/install/uninstall.php b/livesupport/modules/storageServer/var/install/uninstall.php new file mode 100644 index 000000000..e05354e3d --- /dev/null +++ b/livesupport/modules/storageServer/var/install/uninstall.php @@ -0,0 +1,61 @@ +getMessage()."\ndi:\n".$err->getDebugInfo()."\nui:\n".$err->getUserInfo()."\n
\n"; + exit; +} + + +PEAR::setErrorHandling(PEAR_ERROR_PRINT, "%s
\n"); +$dbc = DB::connect($config['dsn'], TRUE); +if(PEAR::isError($dbc)){ + echo "Database connection problem.\n"; + echo "Check if database '{$config['dsn']['database']}' exists with corresponding permissions.\n"; + echo "Database access is defined by 'dsn' values in conf.php.\n"; + exit; +} + +$dbc->setFetchMode(DB_FETCHMODE_ASSOC); +$gb = &new GreenBox(&$dbc, $config); + +# $dbc->setErrorHandling(PEAR_ERROR_RETURN); +echo "Trying to uninstall all ...\n"; +$gb->uninstall(); + +$dbc->disconnect(); +?> diff --git a/livesupport/modules/storageServer/var/tests/analyze.php b/livesupport/modules/storageServer/var/tests/analyze.php new file mode 100755 index 000000000..afa835fb1 --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/analyze.php @@ -0,0 +1,25 @@ +#!/usr/bin/php -q +insert('./ex1.mp3', 'mp3'); +#$r = $rmd->test('./ex1.mp3', './ex2.wav', '../access/xyz.ext'); + +$rmd =& new RawMediaData($gunid, '../stor/'.substr($gunid, 0, 3)); +$r = $rmd->analyze(); + +echo "r=$r (".gettype($r).")\n"; +if(PEAR::isError($r)) echo"ERR: ".$r->getMessage()."\n".$r->getUserInfo()."\n"; +if(is_array($r)) print_r($r); +echo"\n"; +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/tests/ex1.mp3 b/livesupport/modules/storageServer/var/tests/ex1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d7534cf61cfb713c7f67456b2c79d5fe8696a583 GIT binary patch literal 48612 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 literal 0 HcmV?d00001 diff --git a/livesupport/modules/storageServer/var/tests/ex2.ogg b/livesupport/modules/storageServer/var/tests/ex2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..13d09712b63a400f9746f660282875dc33c2751a GIT binary patch literal 5354 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 literal 0 HcmV?d00001 diff --git a/livesupport/modules/storageServer/var/tests/ex2.wav b/livesupport/modules/storageServer/var/tests/ex2.wav new file mode 100644 index 0000000000000000000000000000000000000000..785f4edb369671bf3e288ac68e462a67782f4bff GIT binary patch literal 19564 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 literal 0 HcmV?d00001 diff --git a/livesupport/modules/storageServer/var/tests/index.php b/livesupport/modules/storageServer/var/tests/index.php new file mode 100644 index 000000000..a95cd977e --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/index.php @@ -0,0 +1,34 @@ + diff --git a/livesupport/modules/storageServer/var/tests/mdata1.xml b/livesupport/modules/storageServer/var/tests/mdata1.xml new file mode 100644 index 000000000..2d31ab485 --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/mdata1.xml @@ -0,0 +1,33 @@ + + +File Title txt +Alternative File Title txt +Keywords: qwe, asd, zcx +Abstract txt +2004-05-21 +2004-05-22 +2004-05-23 +2004-05-24 +2004-05-25 +Sound +audio/mpeg +123 +online +streamonthefly: +http://some.url.mdlf.org/ +Spatial Coverage +Temporal Covarage +Episode Title txt +Episode sequence + + Editor + John X + 123456789 + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/tests/mdata2.xml b/livesupport/modules/storageServer/var/tests/mdata2.xml new file mode 100644 index 000000000..baf0fb19f --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/mdata2.xml @@ -0,0 +1,33 @@ + + +File2 Title txt +Alternative File2 Title txt +Keywords: qwe, asd, zcx +Abstract txt +2004-05-21 +2004-05-22 +2004-05-23 +2004-05-24 +2004-05-25 +Sound +audio/mpeg +123 +online +streamonthefly: +http://some.url.mdlf.org/ +Spatial Coverage +Temporal Covarage +Episode Title txt +Episode sequence + + Editor + John X + 123456789 + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/tests/mdata3.xml b/livesupport/modules/storageServer/var/tests/mdata3.xml new file mode 100644 index 000000000..3ab610952 --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/mdata3.xml @@ -0,0 +1,16 @@ + + +File Title3 txt +Alternative File Title txt + + Author + John Y + 234 + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/tests/question.wav b/livesupport/modules/storageServer/var/tests/question.wav new file mode 100644 index 0000000000000000000000000000000000000000..6c8b2a0cbfbd7dde822e8b94858217ac437034c6 GIT binary patch literal 4189 zcmeHI$#SdO74@5Cm_JB+3OHvowdx_FNjwXl9k&rPQaRsadsMf!GnP zZjRSF5XKWBI9-o{IvKOhu5C9eJDMw-%e6);ist>p^%RTDh%>icR~pOlR7%cW<8m}5 z;{ElK%G1$82lK5l9ZwCS;Uz`M+2MU33FEO4*6+tmrbY|*a6QM`WHfi`u1!s5#F*8! z%hmZCtCiPlsj+W!C-2&=!MshatnF%hHk#<=<&s%5urAMSffi#{-CVk@#Zyx$K6QI6 z(G%Hg+G8Y6VeV~v$=6egwX^f3LQ9%a{iZ#HB25ckc{%3#jGCLt=~B2nJ(1$N-KVgx zCCV?S)MBZrmLJ=#FHp1?)otU+6IkKCJ;es(StT)N$Lu4}mZy2|G%T1uO)j=2#drCnFq{OfF$UT-xL-sp95zBL{wHxNB&m-ifVuX80n-)dK;#=Kj>uMZJq1elD1sdY>R+##`yJNi30BnQ?Q-9C<2eLA}qB zIOj}vdnmE8Txec?%6)w?S5VSyV@H^YPEyq|mS>#d=ZB4_FF6(E&DKM!`O1m6`vj}( zN_Wd+flYoXqpYq1OPonoxNB0hW`#8_4~4tt7aH2^3eONZ4LtL}T5$>rt1@;ZPICNm z8zPxoSy55NSmgvW$ae`^bE4swy9{akO7n_pgB4yd-Mq@(wICSan0gw|YhGGyd_&;Z zcvIz`wGv=}vJ8+cNOn|~k+T+L!z;=Z8>^LxvNHD!SNrUg zS65XN#cARhs$!rhjeU>|On|NSMu`hXI()+=E?PerO)(rFY9K?=9XCmQu+|(Lry*Fc zZ5*Znq=B`UMjp~s9eHu$fd)anwqBbkNMaWmx`o^@a*?f@4hlmLAzinTAB7m10Ng0T zy@sR6N0zQTC{57lp*h1Sd!{bOU)Iq?tVc$pmkZJjlNT5`)9g?_>J1 z#2N>o57vfFY9M477V?7tj0~_20%+1OAt>;WW12SdJm}W89pri*G-=xiC>J>dxUM^B z_&b{ClJHM?PXho@1vE(Ood!U>F=&JLAXy}z6tSQh0TwtC37Cfjjr8>$0Ekp|AO@oE z|GY511M~qt&{KO*&+rF<@CWtaJ~4!Xa(xio?_fZnJf+VJ1Pxg|Aj-ge0pySWy9?$b zBloKw0Aa`&@LWIw_?+D(=<9;WdFrXB24{>=cY|-jdjpW**pQ%y`B0EBL?gZQpfCJj zhC4{liyWH18tD>~vVFVX1H_RWf8t2qgKwhy<44XR3BqJ>oAl;BsONCKHk2clUv@DZ zDALMfP4~@#(=hiP^m7l}CGQVtL*0jCzrbfp4*)HZ-e6<``lmBYLehqyyGGi+N0Xh9 zm+WbIAK-5A%o`>ddID`2;L-OmCD1(}I~iJeR{0FRuB3N-?DUgFo)_u+VLdnkGt}r; z(vRB*eeLKw)F-Fj;0FVI_j!?N@0QOe_}}w~CVv$3&zDk4`m?!9q_57EQFEu`t56h>)Q~ZI}_x23>34U(i K=LY^48~6`~3n8=s literal 0 HcmV?d00001 diff --git a/livesupport/modules/storageServer/var/tests/srch_cri1.xml b/livesupport/modules/storageServer/var/tests/srch_cri1.xml new file mode 100644 index 000000000..417a11b00 --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/srch_cri1.xml @@ -0,0 +1,14 @@ + + +File% + + Author + John Y + + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/tests/testStorage.xml b/livesupport/modules/storageServer/var/tests/testStorage.xml new file mode 100644 index 000000000..4b7a4066c --- /dev/null +++ b/livesupport/modules/storageServer/var/tests/testStorage.xml @@ -0,0 +1,12 @@ + + + + + + +]> + + + diff --git a/livesupport/modules/storageServer/var/xmlrpc/index.php b/livesupport/modules/storageServer/var/xmlrpc/index.php new file mode 100644 index 000000000..eabc310c2 --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/index.php @@ -0,0 +1,34 @@ + diff --git a/livesupport/modules/storageServer/var/xmlrpc/testRunner.sh b/livesupport/modules/storageServer/var/xmlrpc/testRunner.sh new file mode 100755 index 000000000..fc0f7af8b --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/testRunner.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +#------------------------------------------------------------------------------- +# Copyright (c) 2004 Media Development Loan Fund +# +# This file is part of the LiveSupport project. +# http://livesupport.campware.org/ +# To report bugs, send an e-mail to bugs@campware.org +# +# LiveSupport is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LiveSupport is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LiveSupport; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# Author : $Author: tomas $ +# Version : $Revision: 1.1 $ +# Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/storageServer/var/xmlrpc/testRunner.sh,v $ +#------------------------------------------------------------------------------- + +COMM=$1 +shift +GUNID=$1 + +#XMLRPC=http://localhost:80/tom/work/mdlf/livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php +XMLRPC=http://localhost:80/livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php +echo "XMLRPC server URL (check it in troubles):" +echo $XMLRPC + +TESTDIR=`dirname $0` +XR_CLI="$TESTDIR/xr_cli_test.py -s $XMLRPC" + +login() { + echo "login:" + SESSID=`$XR_CLI login root q` + echo "sessid: $SESSID" +} + +test() { + echo "test:" + $XR_CLI test $SESSID stringForUppercase +} + +existsAudioClip() { + echo "existsAudioClip:" + $XR_CLI existsAudioClip $SESSID $GUNID +} + +accessRawAudioData() { + echo "accessRawAudioData:" + FPATH=`$XR_CLI accessRawAudioData $SESSID $GUNID` + FPATH="" + FPATH=`echo "$FPATH" | php -q` + echo $FPATH + ls -l $FPATH + echo "releaseRawAudioData:" + $XR_CLI releaseRawAudioData $SESSID $FPATH +#$XR_CLI getAudioClip $SESSID $GUNID +} + +storeAudioClip() { + echo "storeAudioClip:" + MEDIA=../tests/ex1.mp3 + METADATA=../tests/testStorage.xml + RGUNID=`$XR_CLI storeAudioClip "$SESSID" 'X' "$MEDIA" "$METADATA"` + echo $RGUNID +} + +deleteAudioClip() { + echo "deleteAudioClip:" + $XR_CLI deleteAudioClip $SESSID $GUNID +} + +updateAudioClipMetadata() { + echo "updateAudioClipMetadata:" + $XR_CLI updateAudioClipMetadata $SESSID $GUNID '../tests/mdata3.xml' +} + +getAudioClip() { + echo "getAudioClip:" + $XR_CLI getAudioClip $SESSID $GUNID | ./urldecode +} + +searchMetadata() { + echo "searchMetadata:" +# $XR_CLI searchMetadata $SESSID '../tests/srch_cri1.xml' + $XR_CLI searchMetadata $SESSID 'John %' +} + +logout() { + echo "logout:" + $XR_CLI logout $SESSID +} + +usage(){ + echo "Usage: $0 [args]" + echo -e "commands:\n test\n existsAudioClip\n accessRawAudioData" + echo -e " storeAudioClip\n deleteAudioClip\n updateAudioClipMetadata" + echo -e " getAudioClip\n searchMetadata\n" +} + +if [ "$COMM" == "test" ]; then + login + test + logout +elif [ "$COMM" == "existsAudioClip" ]; then + login + existsAudioClip + logout +elif [ "$COMM" == "accessRawAudioData" ]; then + login + accessRawAudioData + logout +elif [ "$COMM" == "storeAudioClip" ]; then + login + storeAudioClip + logout +elif [ "$COMM" == "deleteAudioClip" ]; then + login + deleteAudioClip + logout +elif [ "$COMM" == "updateAudioClipMetadata" ]; then + login + updateAudioClipMetadata + logout +elif [ "$COMM" == "getAudioClip" ]; then + login + getAudioClip + logout +elif [ "$COMM" == "searchMetadata" ]; then + login + searchMetadata + logout +elif [ "x$COMM" == "x" ]; then + login + storeAudioClip + GUNID=$RGUNID + accessRawAudioData + deleteAudioClip + logout +elif [ "$COMM" == "help" ]; then + usage +else + echo "Unknown command" + usage +fi diff --git a/livesupport/modules/storageServer/var/xmlrpc/urldecode b/livesupport/modules/storageServer/var/xmlrpc/urldecode new file mode 100755 index 000000000..172c8f73c --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/urldecode @@ -0,0 +1,7 @@ +#!/usr/bin/php -q + \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/xmlrpc/xmlrpc.inc b/livesupport/modules/storageServer/var/xmlrpc/xmlrpc.inc new file mode 100644 index 000000000..813b29fc8 --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/xmlrpc.inc @@ -0,0 +1,1489 @@ + +// $Id: xmlrpc.inc,v 1.1 2004/09/12 21:59:32 tomas Exp $ + + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of the "XML-RPC for PHP" nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + if (!function_exists('xml_parser_create')) + { + // Win 32 fix. From: 'Leo West' + if($WINDIR) + { + dl('php3_xml.dll'); + } + else + { + dl('xml.so'); + } + } + + $xmlrpcI4='i4'; + $xmlrpcInt='int'; + $xmlrpcBoolean='boolean'; + $xmlrpcDouble='double'; + $xmlrpcString='string'; + $xmlrpcDateTime='dateTime.iso8601'; + $xmlrpcBase64='base64'; + $xmlrpcArray='array'; + $xmlrpcStruct='struct'; + + $xmlrpcTypes=array( + $xmlrpcI4 => 1, + $xmlrpcInt => 1, + $xmlrpcBoolean => 1, + $xmlrpcString => 1, + $xmlrpcDouble => 1, + $xmlrpcDateTime => 1, + $xmlrpcBase64 => 1, + $xmlrpcArray => 2, + $xmlrpcStruct => 3 + ); + + $xmlEntities=array( + 'amp' => '&', + 'quot' => '"', + 'lt' => '<', + 'gt' => '>', + 'apos' => "'" + ); + + $xmlrpcerr['unknown_method']=1; + $xmlrpcstr['unknown_method']='Unknown method'; + $xmlrpcerr['invalid_return']=2; + $xmlrpcstr['invalid_return']='Invalid return payload: enabling debugging to examine incoming payload'; + $xmlrpcerr['incorrect_params']=3; + $xmlrpcstr['incorrect_params']='Incorrect parameters passed to method'; + $xmlrpcerr['introspect_unknown']=4; + $xmlrpcstr['introspect_unknown']="Can't introspect: method unknown"; + $xmlrpcerr['http_error']=5; + $xmlrpcstr['http_error']="Didn't receive 200 OK from remote server."; + $xmlrpcerr['no_data']=6; + $xmlrpcstr['no_data']='No data received from server.'; + $xmlrpcerr['no_ssl']=7; + $xmlrpcstr['no_ssl']='No SSL support compiled in.'; + $xmlrpcerr['curl_fail']=8; + $xmlrpcstr['curl_fail']='CURL error'; + + + $xmlrpcerr["multicall_notstruct"] = 9; + $xmlrpcstr["multicall_notstruct"] = "system.multicall expected struct"; + $xmlrpcerr["multicall_nomethod"] = 10; + $xmlrpcstr["multicall_nomethod"] = "missing methodName"; + $xmlrpcerr["multicall_notstring"] = 11; + $xmlrpcstr["multicall_notstring"] = "methodName is not a string"; + $xmlrpcerr["multicall_recursion"] = 12; + $xmlrpcstr["multicall_recursion"] = "recursive system.multicall forbidden"; + $xmlrpcerr["multicall_noparams"] = 13; + $xmlrpcstr["multicall_noparams"] = "missing params"; + $xmlrpcerr["multicall_notarray"] = 14; + $xmlrpcstr["multicall_notarray"] = "params is not an array"; + + $xmlrpc_defencoding='UTF-8'; + + $xmlrpcName='XML-RPC for PHP'; + $xmlrpcVersion='1.0.99'; + + // let user errors start at 800 + $xmlrpcerruser=800; + // let XML parse errors start at 100 + $xmlrpcerrxml=100; + + // formulate backslashes for escaping regexp + $xmlrpc_backslash=chr(92).chr(92); + + // used to store state during parsing + // quick explanation of components: + // st - used to build up a string for evaluation + // ac - used to accumulate values + // qt - used to decide if quotes are needed for evaluation + // cm - used to denote struct or array (comma needed) + // isf - used to indicate a fault + // lv - used to indicate "looking for a value": implements + // the logic to allow values with no types to be strings + // params - used to store parameters in method calls + // method - used to store method name + + $_xh=array(); + + function xmlrpc_entity_decode($string) + { + $top=split('&', $string); + $op=''; + $i=0; + while($i "; + break; + case 'BOOLEAN': + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + if ($_xh[$parser]['ac']=='1') + { + $_xh[$parser]['ac']='true'; + } + else + { + $_xh[$parser]['ac']='false'; + $_xh[$parser]['vt']=strtolower($name); + // Drop through intentionally. + } + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($_xh[$parser]['qt']==1) + { + // we use double quotes rather than single so backslashification works OK + $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; + } + elseif ($_xh[$parser]['qt']==2) + { + $_xh[$parser]['st'].="base64_decode('". $_xh[$parser]['ac'] . "')"; + } + elseif ($name=='BOOLEAN') + { + $_xh[$parser]['st'].=$_xh[$parser]['ac']; + } + else + { + // we have an I4, INT or a DOUBLE + // we must check that only 0123456789-. are characters here + if (!ereg("^\-?[0123456789 \t\.]+$", $_xh[$parser]['ac'])) + { + // TODO: find a better way of throwing an error + // than this! + error_log('XML-RPC: non numeric value received in INT or DOUBLE'); + $_xh[$parser]['st'].='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $_xh[$parser]['st'].=$_xh[$parser]['ac']; + } + } + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + $_xh[$parser]['lv']=3; // indicate we've found a value + break; + case 'VALUE': + // deal with a string value + if (strlen($_xh[$parser]['ac'])>0 && + $_xh[$parser]['vt']==$xmlrpcString) + { + $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; + } + // This if() detects if no scalar was inside + // and pads an empty ''. + if($_xh[$parser]['st'][strlen($_xh[$parser]['st'])-1] == '(') + { + $_xh[$parser]['st'].= '""'; + } + $_xh[$parser]['st'].=", '" . $_xh[$parser]['vt'] . "')"; + if ($_xh[$parser]['cm']) + { + $_xh[$parser]['st'].=','; + } + break; + case 'MEMBER': + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + break; + case 'DATA': + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + break; + case 'PARAM': + $_xh[$parser]['params'][]=$_xh[$parser]['st']; + break; + case 'METHODNAME': + $_xh[$parser]['method']=ereg_replace("^[\n\r\t ]+", '', $_xh[$parser]['ac']); + break; + case 'BOOLEAN': + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + if ($_xh[$parser]['ac']=='1') + { + $_xh[$parser]['ac']='true'; + } + else + { + $_xh[$parser]['ac']='false'; + $_xh[$parser]['vt']=strtolower($name); + } + break; + default: + break; + } + // if it's a valid type name, set the type + if (isset($xmlrpcTypes[strtolower($name)])) + { + $_xh[$parser]['vt']=strtolower($name); + } + } + + function xmlrpc_cd($parser, $data) + { + global $_xh, $xmlrpc_backslash; + + //if (ereg("^[\n\r \t]+$", $data)) return; + // print "adding [${data}]\n"; + + if ($_xh[$parser]['lv']!=3) + { + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + if ($_xh[$parser]['lv']==1) + { + // if we've found text and we're just in a then + // turn quoting on, as this will be a string + $_xh[$parser]['qt']=1; + // and say we've found a value + $_xh[$parser]['lv']=2; + } + if(!@isset($_xh[$parser]['ac'])) + { + $_xh[$parser]['ac'] = ''; + } + $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data))); + } + } + + function xmlrpc_dh($parser, $data) + { + global $_xh; + if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') + { + if ($_xh[$parser]['lv']==1) + { + $_xh[$parser]['qt']=1; + $_xh[$parser]['lv']=2; + } + $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data))); + } + } + + class xmlrpc_client + { + var $path; + var $server; + var $port; + var $errno; + var $errstring; + var $debug=0; + var $username=''; + var $password=''; + var $cert=''; + var $certpass=''; + var $verifypeer=1; + var $verifyhost=1; + var $no_multicall=false; + + function xmlrpc_client($path, $server, $port=0) + { + $this->port=$port; $this->server=$server; $this->path=$path; + } + + function setDebug($in) + { + if ($in) + { + $this->debug=1; + } + else + { + $this->debug=0; + } + } + + function setCredentials($u, $p) + { + $this->username=$u; + $this->password=$p; + } + + function setCertificate($cert, $certpass) + { + $this->cert = $cert; + $this->certpass = $certpass; + } + + function setSSLVerifyPeer($i) + { + $this->verifypeer = $i; + } + + function setSSLVerifyHost($i) + { + $this->verifyhost = $i; + } + + function send($msg, $timeout=0, $method='http') + { + if (is_array($msg)) + { + // $msg is an array of xmlrpcmsg's + return $this->multicall($msg, $timeout, $method); + } + + // where msg is an xmlrpcmsg + $msg->debug=$this->debug; + + if ($method == 'https') + { + return $this->sendPayloadHTTPS($msg, + $this->server, + $this->port, $timeout, + $this->username, $this->password, + $this->cert, + $this->certpass); + } + else + { + return $this->sendPayloadHTTP10($msg, $this->server, $this->port, + $timeout, $this->username, + $this->password); + } + } + + function sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='') + { + global $xmlrpcerr, $xmlrpcstr; + if ($port==0) + { + $port=80; + } + if($timeout>0) + { + $fp=fsockopen($server, $port,$this->errno, $this->errstr, $timeout); + } + else + { + $fp=fsockopen($server, $port,$this->errno, $this->errstr); + } + if (!$fp) + { + $this->errstr='Connect error'; + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'],$xmlrpcstr['http_error']); + return $r; + } + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + // thanks to Grant Rauscher + // for this + $credentials=''; + if ($username!='') + { + $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; + } + + // "Host: ". $this->server . ":" . $this->port . "\r\n" . + + $op= "POST " . $this->path. " HTTP/1.0\r\nUser-Agent: PHP XMLRPC 1.0\r\n" . + "Host: ". $this->server . "\r\n" . + $credentials . + "Content-Type: text/xml\r\nContent-Length: " . + strlen($msg->payload) . "\r\n\r\n" . + $msg->payload; + + if (!fputs($fp, $op, strlen($op))) + { + $this->errstr='Write error'; + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']); + return $r; + } + $resp=$msg->parseResponseFile($fp); + fclose($fp); + return $resp; + } + + // contributed by Justin Miller + // requires curl to be built into PHP + function sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='') + { + global $xmlrpcerr, $xmlrpcstr; + if ($port == 0) + { + $port = 443; + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + if (!function_exists('curl_init')) + { + $this->errstr='SSL unavailable on this install'; + $r=new xmlrpcresp(0, $xmlrpcerr['no_ssl'], $xmlrpcstr['no_ssl']); + return $r; + } + + $curl = curl_init("https://" . $server . ':' . $port . $this->path); + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // results into variable + if ($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC 1.0'); + // required for XMLRPC + curl_setopt($curl, CURLOPT_POST, 1); + // post the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $msg->payload); + // the data + curl_setopt($curl, CURLOPT_HEADER, 1); + // return the header too + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); + // whether to verify remote host's cert + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); + // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); + // required for XMLRPC + if ($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + // timeout is borked + if ($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD,"$username:$password"); + } + // set auth stuff + if ($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + // set cert file + if ($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD,$certpass); + } + // set cert password + + $result = curl_exec($curl); + + if (!$result) + { + $this->errstr='no response'; + $resp=new xmlrpcresp(0, $xmlrpcerr['curl_fail'], $xmlrpcstr['curl_fail']. ': '. curl_error($curl)); + } + else + { + $resp = $msg->parseResponse($result); + } + curl_close($curl); + return $resp; + } + + function multicall($msgs, $timeout=0, $method='http') + { + $results = false; + + if (! $this->no_multicall) + { + $results = $this->_try_multicall($msgs, $timeout, $method); + /* TODO - this is not php3-friendly */ + // if($results !== false) + if($results != false) + { + // Either the system.multicall succeeded, or the send + // failed (e.g. due to HTTP timeout). In either case, + // we're done for now. + return $results; + } + else + { + // system.multicall unsupported by server, + // don't try it next time... + $this->no_multicall = true; + } + } + + // system.multicall is unupported by server: + // Emulate multicall via multiple requests + $results = array(); + //foreach($msgs as $msg) + @reset($msgs); + while(list(,$msg) = @each($msgs)) + { + $results[] = $this->send($msg, $timeout, $method); + } + return $results; + } + + // Attempt to boxcar $msgs via system.multicall. + function _try_multicall($msgs, $timeout, $method) + { + // Construct multicall message + $calls = array(); + //foreach($msgs as $msg) + @reset($msgs); + while(list(,$msg) = @each($msgs)) + { + $call['methodName'] = new xmlrpcval($msg->method(),'string'); + $numParams = $msg->getNumParams(); + $params = array(); + for ($i = 0; $i < $numParams; $i++) + { + $params[$i] = $msg->getParam($i); + } + $call['params'] = new xmlrpcval($params, 'array'); + $calls[] = new xmlrpcval($call, 'struct'); + } + $multicall = new xmlrpcmsg('system.multicall'); + $multicall->addParam(new xmlrpcval($calls, 'array')); + + // Attempt RPC call + $result = $this->send($multicall, $timeout, $method); + if (!is_object($result)) + return ($result || 0); // transport failed + + if ($result->faultCode() != 0) + return false; // system.multicall failed + + // Unpack responses. + $rets = $result->value(); + if ($rets->kindOf() != 'array') + return false; // bad return type from system.multicall + $numRets = $rets->arraysize(); + if ($numRets != count($msgs)) + return false; // wrong number of return values. + + $response = array(); + for ($i = 0; $i < $numRets; $i++) + { + $val = $rets->arraymem($i); + switch ($val->kindOf()) + { + case 'array': + if ($val->arraysize() != 1) + return false; // Bad value + // Normal return value + $response[$i] = new xmlrpcresp($val->arraymem(0)); + break; + case 'struct': + $code = $val->structmem('faultCode'); + if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + return false; + $str = $val->structmem('faultString'); + if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + return false; + $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); + break; + default: + return false; + } + } + return $response; + } + } // end class xmlrpc_client + + class xmlrpcresp + { + var $xv; + var $fn; + var $fs; + var $hdrs; + + function xmlrpcresp($val, $fcode=0, $fstr='',$waserror=0) + { + //print "waserror=$waserror
"; + if(($fcode != 0) || ($waserror != 0)) + { + $this->xv = 0; + $this->fn = $fcode; + $this->fs = htmlspecialchars($fstr); + } + else + { + $this->xv = $val; + $this->fn = 0; + } + } + + function faultCode() + { + if(isset($this->fn)) + { + if($this->fn == 0) + { + return -1; + } + else + { + return $this->fn; + } + } + else + { + return 0; + } + } + + function faultString() + { + return $this->fs; + } + + function value() + { + return $this->xv; + } + + function serialize() + { + $rs="\n"; + if ($this->fn) + { + $rs.=' + + + +faultCode +' . $this->fn . ' + + +faultString +' . $this->fs . ' + + + +'; + } + else + { + $rs.="\n\n" . $this->xv->serialize() . + "\n"; + } + $rs.="\n"; + return $rs; + } + } + + class xmlrpcmsg + { + var $payload; + var $methodname; + var $params=array(); + var $debug=0; + + function xmlrpcmsg($meth, $pars=0) + { + $this->methodname=$meth; + if (is_array($pars) && sizeof($pars)>0) + { + for($i=0; $iaddParam($pars[$i]); + } + } + } + + function xml_header() + { + return "\n\n"; + } + + function xml_footer() + { + return "\n"; + } + + function createPayload() + { + $this->payload=$this->xml_header(); + $this->payload.="" . $this->methodname . "\n"; + // if (sizeof($this->params)) { + $this->payload.="\n"; + for($i=0; $iparams); $i++) + { + $p=$this->params[$i]; + $this->payload.="\n" . $p->serialize() . + "\n"; + } + $this->payload.="\n"; + // } + $this->payload.=$this->xml_footer(); + $this->payload=str_replace("\n", "\r\n", $this->payload); + } + + function method($meth='') + { + if ($meth!='') + { + $this->methodname=$meth; + } + return $this->methodname; + } + + function serialize() + { + $this->createPayload(); + return $this->payload; + } + + function addParam($par) { $this->params[]=$par; } + function getParam($i) { return $this->params[$i]; } + function getNumParams() { return sizeof($this->params); } + + function parseResponseFile($fp) + { + $ipd=''; + while($data=fread($fp, 32768)) + { + $ipd.=$data; + } + return $this->parseResponse($ipd); + } + + function parseResponse($data='') + { + global $_xh,$xmlrpcerr,$xmlrpcstr; + global $xmlrpc_defencoding; + + $parser = xml_parser_create($xmlrpc_defencoding); + + $_xh[$parser]=array(); + + $_xh[$parser]['st']=''; + $_xh[$parser]['cm']=0; + $_xh[$parser]['isf']=0; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=''; + $_xh[$parser]['ha']=''; + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + $xmlrpc_value=new xmlrpcval; + + $hdrfnd = 0; + if($this->debug) + { + //by maHo, replaced htmlspecialchars with htmlentities + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n
"; + } + if($data == '') + { + error_log('No response received from server.'); + $r = new xmlrpcresp(0, $xmlrpcerr['no_data'], $xmlrpcstr['no_data']); + xml_parser_free($parser); + return $r; + } + // see if we got an HTTP 200 OK, else bomb + // but only do this if we're using the HTTP protocol. + if(ereg("^HTTP",$data) && !ereg("^HTTP/[0-9\.]+ 200 ", $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('HTTP error, got response: ' .$errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']. ' (' . $errstr . ')'); + xml_parser_free($parser); + return $r; + } + + // if using HTTP, then gotta get rid of HTTP headers here + // and we store them in the 'ha' bit of our data array + if (ereg("^HTTP", $data)) + { + $ar=explode("\r\n", $data); + $newdata=''; + $hdrfnd=0; + for ($i=0; $i0) + { + $_xh[$parser]['ha'].=$ar[$i]. "\r\n"; + } + else + { + $hdrfnd=1; + } + } + else + { + $newdata.=$ar[$i] . "\r\n"; + } + } + $data=$newdata; + } + + if ($this->debug) + { + @reset($_xh[$parser]['ha']); + while (list($x,$header) = @each($_xh[$parser]['ha'])) + { + echo "HEADER: $header\n"; + } + } + // end Patch + if (!xml_parse($parser, $data, sizeof($data))) + { + // thanks to Peter Kocks + if((xml_get_current_line_number($parser)) == 1) + { + $errstr = 'XML error at line 1, check URL'; + } + else + { + $errstr = sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser)); + error_log($errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return']); + xml_parser_free($parser); + echo $errstr; + return $r; + } + } + xml_parser_free($parser); + if ($this->debug) + { + print "
---EVALING---[" . 
+				strlen($_xh[$parser]['st']) . " chars]---\n" . 
+				htmlspecialchars($_xh[$parser]['st']) . ";\n---END---
"; + } + if (strlen($_xh[$parser]['st'])==0) + { + // then something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + $r=new xmlrpcresp(0, $xmlrpcerr["invalid_return"], + $xmlrpcstr["invalid_return"]); + } + else + { + eval('$v=' . $_xh[$parser]['st'] . '; $allOK=1;'); + if ($_xh[$parser]['isf']) + { + $f=$v->structmem('faultCode'); + $fs=$v->structmem('faultString'); + + $r = new xmlrpcresp($v, $f->scalarval(), + $fs->scalarval(),1 /* even if errornum=0, indicate that there is error. modified by maHo */ + ); + + } + else + { + $r=new xmlrpcresp($v); + } + } + $r->hdrs = @split("\r?\n", $_xh[$parser]['ha'][1]); + return $r; + } + } + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + + function xmlrpcval($val=-1, $type='') + { + global $xmlrpcTypes; + $this->me=array(); + $this->mytype=0; + if ($val!=-1 || $type!='') + { + if ($type=='') + { + $type='string'; + } + if ($xmlrpcTypes[$type]==1) + { + $this->addScalar($val,$type); + } + elseif ($xmlrpcTypes[$type]==2) + { + $this->addArray($val); + } + elseif ($xmlrpcTypes[$type]==3) + { + $this->addStruct($val); + } + } + } + + function addScalar($val, $type='string') + { + global $xmlrpcTypes, $xmlrpcBoolean; + + if ($this->mytype==1) + { + echo 'xmlrpcval: scalar can have only one value
'; + return 0; + } + $typeof=$xmlrpcTypes[$type]; + if ($typeof!=1) + { + echo 'xmlrpcval: not a scalar type (${typeof})
'; + return 0; + } + + if ($type==$xmlrpcBoolean) + { + if (strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) + { + $val=1; + } + else + { + $val=0; + } + } + + if ($this->mytype==2) + { + // we're adding to an array here + $ar=$this->me['array']; + $ar[]=new xmlrpcval($val, $type); + $this->me['array']=$ar; + } + else + { + // a scalar, so set the value and remember we're scalar + $this->me[$type]=$val; + $this->mytype=$typeof; + } + return 1; + } + + function addArray($vals) + { + global $xmlrpcTypes; + if ($this->mytype!=0) + { + echo 'xmlrpcval: already initialized as a [' . $this->kindOf() . ']
'; + return 0; + } + + $this->mytype=$xmlrpcTypes['array']; + $this->me['array']=$vals; + return 1; + } + + function addStruct($vals) + { + global $xmlrpcTypes; + if ($this->mytype!=0) + { + echo 'xmlrpcval: already initialized as a [' . $this->kindOf() . ']
'; + return 0; + } + $this->mytype=$xmlrpcTypes['struct']; + $this->me['struct']=$vals; + return 1; + } + + function dump($ar) + { + reset($ar); + while ( list( $key, $val ) = each( $ar ) ) + { + echo "$key => $val
"; + if ($key == 'array') + { + while ( list( $key2, $val2 ) = each( $val ) ) + { + echo "-- $key2 => $val2
"; + } + } + } + } + + function kindOf() + { + switch($this->mytype) + { + case 3: + return 'struct'; + break; + case 2: + return 'array'; + break; + case 1: + return 'scalar'; + break; + default: + return 'undef'; + } + } + + function serializedata($typ, $val) + { + $rs=''; + global $xmlrpcTypes, $xmlrpcBase64, $xmlrpcString, + $xmlrpcBoolean; + switch($xmlrpcTypes[$typ]) + { + case 3: + // struct + $rs.="\n"; + reset($val); + while(list($key2, $val2)=each($val)) + { + $rs.="${key2}\n"; + $rs.=$this->serializeval($val2); + $rs.="\n"; + } + $rs.=''; + break; + case 2: + // array + $rs.="\n\n"; + for($i=0; $iserializeval($val[$i]); + } + $rs.="\n"; + break; + case 1: + switch ($typ) + { + case $xmlrpcBase64: + $rs.="<${typ}>" . base64_encode($val) . ""; + break; + case $xmlrpcBoolean: + $rs.="<${typ}>" . ($val ? "1" : "0") . ""; + break; + case $xmlrpcString: + $rs.="<${typ}>" . rawurlencode($val). ""; // micsik + //$rs.="<${typ}>" . htmlspecialchars($val). ""; + break; + default: + $rs.="<${typ}>${val}"; + } + break; + default: + break; + } + return $rs; + } + + function serialize() + { + return $this->serializeval($this); + } + + function serializeval($o) + { + global $xmlrpcTypes; + $rs=''; + $ar=$o->me; + reset($ar); + list($typ, $val) = each($ar); + $rs.=''; + $rs.=$this->serializedata($typ, $val); + $rs.="\n"; + return $rs; + } + + function structmem($m) + { + $nv=$this->me['struct'][$m]; + return $nv; + } + + function structreset() + { + reset($this->me['struct']); + } + + function structeach() + { + return each($this->me['struct']); + } + + function getval() + { + // UNSTABLE + global $xmlrpcBoolean, $xmlrpcBase64; + reset($this->me); + list($a,$b)=each($this->me); + // contributed by I Sofer, 2001-03-24 + // add support for nested arrays to scalarval + // i've created a new method here, so as to + // preserve back compatibility + + if (is_array($b)) + { + @reset($b); + while(list($id,$cont) = @each($b)) + { + $b[$id] = $cont->scalarval(); + } + } + + // add support for structures directly encoding php objects + if (is_object($b)) + { + $t = get_object_vars($b); + @reset($t); + while(list($id,$cont) = @each($t)) + { + $t[$id] = $cont->scalarval(); + } + @reset($t); + while(list($id,$cont) = @each($t)) + { + eval('$b->'.$id.' = $cont;'); + } + } + // end contrib + return $b; + } + + function scalarval() + { + global $xmlrpcBoolean, $xmlrpcBase64; + reset($this->me); + list($a,$b)=each($this->me); + return rawurldecode($b); // micsik + } + + function scalartyp() + { + global $xmlrpcI4, $xmlrpcInt; + reset($this->me); + list($a,$b)=each($this->me); + if ($a==$xmlrpcI4) + { + $a=$xmlrpcInt; + } + return $a; + } + + function arraymem($m) + { + $nv=$this->me['array'][$m]; + return $nv; + } + + function arraysize() + { + reset($this->me); + list($a,$b)=each($this->me); + return sizeof($b); + } + } + + // date helpers + function iso8601_encode($timet, $utc=0) + { + // return an ISO8601 encoded string + // really, timezones ought to be supported + // but the XML-RPC spec says: + // + // "Don't assume a timezone. It should be specified by the server in its + // documentation what assumptions it makes about timezones." + // + // these routines always assume localtime unless + // $utc is set to 1, in which case UTC is assumed + // and an adjustment for locale is made when encoding + if (!$utc) + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + if (function_exists('gmstrftime')) + { + // gmstrftime doesn't exist in some versions + // of PHP + $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z')); + } + } + return $t; + } + + function iso8601_decode($idate, $utc=0) + { + // return a timet in the localtime, or UTC + $t=0; + if (ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs)) + { + if ($utc) + { + $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + else + { + $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + } + return $t; + } + + /**************************************************************** + * xmlrpc_decode takes a message in PHP xmlrpc object format and * + * tranlates it into native PHP types. * + * * + * author: Dan Libby (dan@libby.com) * + ****************************************************************/ + function xmlrpc_decoder($xmlrpc_val) + { + $kind = $xmlrpc_val->kindOf(); + + if($kind == 'scalar') + { + return $xmlrpc_val->scalarval(); + } + elseif($kind == 'array') + { + $size = $xmlrpc_val->arraysize(); + $arr = array(); + + for($i = 0; $i < $size; $i++) + { + $arr[]=xmlrpc_decoder($xmlrpc_val->arraymem($i)); + } + return $arr; + } + elseif($kind == 'struct') + { + $xmlrpc_val->structreset(); + $arr = array(); + + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $arr[$key] = xmlrpc_decoder($value); + } + return $arr; + } + } + + /**************************************************************** + * xmlrpc_encode takes native php types and encodes them into * + * xmlrpc PHP object format. * + * BUG: All sequential arrays are turned into structs. I don't * + * know of a good way to determine if an array is sequential * + * only. * + * * + * feature creep -- could support more types via optional type * + * argument. * + * * + * author: Dan Libby (dan@libby.com) * + ****************************************************************/ + function xmlrpc_encoder($php_val) + { + global $xmlrpcInt; + global $xmlrpcDouble; + global $xmlrpcString; + global $xmlrpcArray; + global $xmlrpcStruct; + global $xmlrpcBoolean; + + $type = gettype($php_val); + $xmlrpc_val = new xmlrpcval; + + switch($type) + { + case 'array': + case 'object': + $arr = array(); + while (list($k,$v) = each($php_val)) + { + $arr[$k] = xmlrpc_encoder($v); + } + $xmlrpc_val->addStruct($arr); + break; + case 'integer': + $xmlrpc_val->addScalar($php_val, $xmlrpcInt); + break; + case 'double': + $xmlrpc_val->addScalar($php_val, $xmlrpcDouble); + break; + case 'string': + $xmlrpc_val->addScalar($php_val, $xmlrpcString); + break; + // + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean); + break; + // + case 'unknown type': + default: + // giancarlo pinerolo + // it has to return + // an empty object in case (which is already + // at this point), not a boolean. + break; + } + return $xmlrpc_val; + } +?> diff --git a/livesupport/modules/storageServer/var/xmlrpc/xmlrpcs.inc b/livesupport/modules/storageServer/var/xmlrpc/xmlrpcs.inc new file mode 100644 index 000000000..0c3ce2326 --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/xmlrpcs.inc @@ -0,0 +1,450 @@ + +// $Id: xmlrpcs.inc,v 1.1 2004/09/12 21:59:34 tomas Exp $ + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of the "XML-RPC for PHP" nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + // XML RPC Server class + // requires: xmlrpc.inc + + // listMethods: either a string, or nothing + $_xmlrpcs_listMethods_sig=array(array($xmlrpcArray, $xmlrpcString), array($xmlrpcArray)); + $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch'; + function _xmlrpcs_listMethods($server, $m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + $v=new xmlrpcval(); + $dmap=$server->dmap; + $outAr=array(); + for(reset($dmap); list($key, $val)=each($dmap); ) + { + $outAr[]=new xmlrpcval($key, 'string'); + } + $dmap=$_xmlrpcs_dmap; + for(reset($dmap); list($key, $val)=each($dmap); ) + { + $outAr[]=new xmlrpcval($key, 'string'); + } + $v->addArray($outAr); + return new xmlrpcresp($v); + } + + $_xmlrpcs_methodSignature_sig=array(array($xmlrpcArray, $xmlrpcString)); + $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)'; + function _xmlrpcs_methodSignature($server, $m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + + $methName=$m->getParam(0); + $methName=$methName->scalarval(); + if (ereg("^system\.", $methName)) + { + $dmap=$_xmlrpcs_dmap; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + // print "\n"; + if (isset($dmap[$methName])) + { + if ($dmap[$methName]['signature']) + { + $sigs=array(); + $thesigs=$dmap[$methName]['signature']; + for($i=0; $igetParam(0); + $methName=$methName->scalarval(); + if (ereg("^system\.", $methName)) + { + $dmap=$_xmlrpcs_dmap; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + // print "\n"; + if (isset($dmap[$methName])) + { + if ($dmap[$methName]['docstring']) + { + $r=new xmlrpcresp(new xmlrpcval($dmap[$methName]["docstring"]), 'string'); + } + else + { + $r=new xmlrpcresp(new xmlrpcval('', 'string')); + } + } + else + { + $r=new xmlrpcresp(0, $xmlrpcerr['introspect_unknown'], $xmlrpcstr['introspect_unknown']); + } + return $r; + } + + $_xmlrpcs_multicall_sig = array(array($xmlrpcArray, $xmlrpcArray)); + $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details'; + + function _xmlrpcs_multicall_error($err) + { + if (is_string($err)) + { + global $xmlrpcerr, $xmlrpcstr; + $str = $xmlrpcstr["multicall_${err}"]; + $code = $xmlrpcerr["multicall_${err}"]; + } + else + { + $code = $err->faultCode(); + $str = $err->faultString(); + } + $struct['faultCode'] = new xmlrpcval($code, 'int'); + $struct['faultString'] = new xmlrpcval($str, 'string'); + return new xmlrpcval($struct, 'struct'); + } + + function _xmlrpcs_multicall_do_call($server, $call) + { + if ($call->kindOf() != 'struct') + return _xmlrpcs_multicall_error('notstruct'); + $methName = $call->structmem('methodName'); + if (!$methName) + return _xmlrpcs_multicall_error('nomethod'); + if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') + return _xmlrpcs_multicall_error('notstring'); + if ($methName->scalarval() == 'system.multicall') + return _xmlrpcs_multicall_error('recursion'); + + $params = $call->structmem('params'); + if (!$params) + return _xmlrpcs_multicall_error('noparams'); + if ($params->kindOf() != 'array') + return _xmlrpcs_multicall_error('notarray'); + $numParams = $params->arraysize(); + + $msg = new xmlrpcmsg($methName->scalarval()); + for ($i = 0; $i < $numParams; $i++) + $msg->addParam($params->arraymem($i)); + + $result = $server->execute($msg); + + if ($result->faultCode() != 0) + return _xmlrpcs_multicall_error($result); // Method returned fault. + + return new xmlrpcval(array($result->value()), "array"); + } + + function _xmlrpcs_multicall($server, $m) + { + $calls = $m->getParam(0); + $numCalls = $calls->arraysize(); + $result = array(); + + for ($i = 0; $i < $numCalls; $i++) + { + $call = $calls->arraymem($i); + $result[$i] = _xmlrpcs_multicall_do_call($server, $call); + } + + return new xmlrpcresp(new xmlrpcval($result, 'array')); + } + + $_xmlrpcs_dmap=array( + 'system.listMethods' => array( + 'function' => '_xmlrpcs_listMethods', + 'signature' => $_xmlrpcs_listMethods_sig, + 'docstring' => $_xmlrpcs_listMethods_doc), + 'system.methodHelp' => array( + 'function' => '_xmlrpcs_methodHelp', + 'signature' => $_xmlrpcs_methodHelp_sig, + 'docstring' => $_xmlrpcs_methodHelp_doc), + 'system.methodSignature' => array( + 'function' => '_xmlrpcs_methodSignature', + 'signature' => $_xmlrpcs_methodSignature_sig, + 'docstring' => $_xmlrpcs_methodSignature_doc), + 'system.multicall' => array( + 'function' => '_xmlrpcs_multicall', + 'signature' => $_xmlrpcs_multicall_sig, + 'docstring' => $_xmlrpcs_multicall_doc + ) + ); + + $_xmlrpc_debuginfo=''; + function xmlrpc_debugmsg($m) + { + global $_xmlrpc_debuginfo; + $_xmlrpc_debuginfo=$_xmlrpc_debuginfo . $m . "\n"; + } + + class xmlrpc_server + { + var $dmap=array(); + + function xmlrpc_server($dispMap='', $serviceNow=1) + { + global $HTTP_RAW_POST_DATA; + // dispMap is a dispatch array of methods + // mapped to function names and signatures + // if a method + // doesn't appear in the map then an unknown + // method error is generated + /* milosch - changed to make passing dispMap optional. + * instead, you can use the class add_to_map() function + * to add functions manually (borrowed from SOAPX4) + */ + if($dispMap) + { + $this->dmap = $dispMap; + if($serviceNow) + { + $this->service(); + } + } + } + + function serializeDebug() + { + global $_xmlrpc_debuginfo; + if ($_xmlrpc_debuginfo!='') + { + return "\n"; + } + else + { + return ''; + } + } + + function service() + { + global $xmlrpc_defencoding; + + $r=$this->parseRequest(); + $payload='' . "\n" + . $this->serializeDebug() + . $r->serialize(); + Header("Content-type: text/xml\r\nContent-length: " . + strlen($payload)); + print $payload; + } + + /* + add a method to the dispatch map + */ + function add_to_map($methodname,$function,$sig,$doc) + { + $this->dmap[$methodname] = array( + 'function' => $function, + 'signature' => $sig, + 'docstring' => $doc + ); + } + + function verifySignature($in, $sig) + { + for($i=0; $igetNumParams()+1) + { + $itsOK=1; + for($n=0; $n<$in->getNumParams(); $n++) + { + $p=$in->getParam($n); + // print "\n"; + if ($p->kindOf() == 'scalar') + { + $pt=$p->scalartyp(); + } + else + { + $pt=$p->kindOf(); + } + // $n+1 as first type of sig is return type + if ($pt != $cursig[$n+1]) + { + $itsOK=0; + $pno=$n+1; $wanted=$cursig[$n+1]; $got=$pt; + break; + } + } + if ($itsOK) + { + return array(1); + } + } + } + return array(0, "Wanted ${wanted}, got ${got} at param ${pno})"); + } + + function parseRequest($data='') + { + global $_xh,$HTTP_RAW_POST_DATA; + global $xmlrpcerr, $xmlrpcstr, $xmlrpcerrxml, $xmlrpc_defencoding, + $_xmlrpcs_dmap; + + if ($data=="") + { + $data=$HTTP_RAW_POST_DATA; + } + $parser = xml_parser_create($xmlrpc_defencoding); + + $_xh[$parser]=array(); + $_xh[$parser]['st']=''; + $_xh[$parser]['cm']=0; + $_xh[$parser]['isf']=0; + $_xh[$parser]['params']=array(); + $_xh[$parser]['method']=''; + + // decompose incoming XML into request structure + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + if (!xml_parse($parser, $data, 1)) + { + // return XML error as a faultCode + $r=new xmlrpcresp(0, + $xmlrpcerrxml+xml_get_error_code($parser), + sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser))); + xml_parser_free($parser); + } + else + { + xml_parser_free($parser); + $m=new xmlrpcmsg($_xh[$parser]['method']); + // now add parameters in + $plist=""; + for($i=0; $i\n"; + $plist.="$i - " . $_xh[$parser]['params'][$i]. " \n"; + eval('$m->addParam(' . $_xh[$parser]['params'][$i]. ");"); + } + // uncomment this to really see what the server's getting! + // xmlrpc_debugmsg($plist); + + $r = $this->execute($m); + } + return $r; + } + + function execute ($m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + // now to deal with the method + $methName = $m->method(); + $sysCall = ereg("^system\.", $methName); + $dmap = $sysCall ? $_xmlrpcs_dmap : $this->dmap; + + if (!isset($dmap[$methName]['function'])) + { + // No such method + return new xmlrpcresp(0, + $xmlrpcerr['unknown_method'], + $xmlrpcstr['unknown_method']); + } + + // Check signature. + if (isset($dmap[$methName]['signature'])) + { + $sig = $dmap[$methName]['signature']; + list ($ok, $errstr) = $this->verifySignature($m, $sig); + if (!$ok) + { + // Didn't match. + return new xmlrpcresp(0, + $xmlrpcerr['incorrect_params'], + $xmlrpcstr['incorrect_params'] . ": ${errstr}"); + } + } + + $func = $dmap[$methName]['function']; + + if ($sysCall) + { + return call_user_func($func, $this, $m); + } + else + { + return call_user_func($func, $m); + } + } + + function echoInput() + { + global $HTTP_RAW_POST_DATA; + + // a debugging routine: just echos back the input + // packet as a string value + + $r=new xmlrpcresp; + $r->xv=new xmlrpcval( "'Aha said I: '" . $HTTP_RAW_POST_DATA, 'string'); + print $r->serialize(); + } + } +?> diff --git a/livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php b/livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php new file mode 100644 index 000000000..0eaca4b8e --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/xrLocStor.php @@ -0,0 +1,181 @@ +gm:\n".$err->getMessage()."\ndi:\n".$err->getDebugInfo()."\nui:\n".$err->getUserInfo()."\n"; + echo "
BackTrace:\n"; + print_r($err->backtrace); + echo "
\n"; + exit; +} + +$dbc = DB::connect($config['dsn'], TRUE); +$dbc->setFetchMode(DB_FETCHMODE_ASSOC); + +function v2xr($var, $struct=true){ + if(is_array($var)){ + $r = array(); + foreach($var as $k=>$v) if($struct) $r[$k]=v2xr($v); else $r[]=v2xr($v); + return new xmlrpcval($r, ($struct ? "struct" : "array")); + }else if(is_int($var)){ + return new xmlrpcval($var, "int"); + }else if(is_bool($var)){ + return new xmlrpcval($var, "boolean"); + }else{ + return new xmlrpcval($var, "string"); + } +} + +class XR_LocStor extends LocStor{ + function _xr_getPars($input) + { + $p = $input->getParam(0); + if(isset($p) && $p->scalartyp()=="struct"){ + $p->structreset(); $r = array(); + while(list($k,$v) = $p->structeach()){ $r[$k] = $v->scalarval(); } + return array(TRUE, $r); + } + else return array(FALSE, new xmlrpcresp(0, 801, "xr_login: wrong 1st parameter, struct expected.")); + } + function xr_test($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + return new xmlrpcresp(v2xr(array( + 'str'=>strtoupper($r['teststring']), + 'login'=>$this->getSessLogin($r['sessid']), + 'sessid'=>$r['sessid'] + ), true)); + } + function xr_authenticate($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->authenticate($r['login'], $r['pass']); + return new xmlrpcresp(new xmlrpcval($res, "boolean")); + } + function xr_login($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + if(!($res = $this->login($r['login'], $r['pass']))) + return new xmlrpcresp(0, 802, "xr_login: login failed - incorrect username or password."); + else + return new xmlrpcresp(v2xr($res, false)); + } + function xr_logout($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->logout($r['sessid']); + if(!PEAR::isError($res)) return new xmlrpcresp(v2xr('Bye', false)); + else return new xmlrpcresp(0, 803, "xr_logout: logout failed - not logged."); + } + function xr_existsAudioClip($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; +# $this->debugLog(join(', ', $r)); + $res = $this->existsAudioClip($r['sessid'], $r['gunid']); +# $this->debugLog($res); + if(PEAR::isError($res)) + return new xmlrpcresp(0, 803, "xr_existsAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + return new xmlrpcresp(new xmlrpcval($res, "boolean")); + } + function xr_storeAudioClip($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->storeAudioClip($r['sessid'], $r['gunid'], $r['mediaFileLP'], $r['mdataFileLP']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "string")); + else return new xmlrpcresp(0, 803, "xr_storeAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_deleteAudioClip($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->deleteAudioClip($r['sessid'], $r['gunid']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "boolean")); + else return new xmlrpcresp(0, 803, "xr_deleteAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_updateAudioClipMetadata($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->updateAudioClipMetadata($r['sessid'], $r['gunid'], $r['mdataFileLP']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "boolean")); + else return new xmlrpcresp(0, 803, "xr_updateAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_searchMetadata($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->searchMetadata($r['sessid'], $r['criteria']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "boolean")); + else return new xmlrpcresp(0, 803, "xr_searchAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_accessRawAudioData($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->accessRawAudioData($r['sessid'], $r['gunid']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "string")); + else return new xmlrpcresp(0, 803, "xr_accessRawAudioData: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_releaseRawAudioData($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->releaseRawAudioData($r['sessid'], $r['tmpLink']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "boolean")); + else return new xmlrpcresp(0, 803, "xr_releaseRawAudioData: ".$res->getMessage()." ".$res->getUserInfo()); + } + function xr_getAudioClip($input) + { + list($ok, $r) = $this->_xr_getPars($input); + if(!$ok) return $r; + $res = $this->getAudioClip($r['sessid'], $r['gunid'], $r['metaData']); + if(!PEAR::isError($res)) return new xmlrpcresp(new xmlrpcval($res, "string")); + else return new xmlrpcresp(0, 803, "xr_getAudioClip: ".$res->getMessage()." ".$res->getUserInfo()); + } +} + +$locStor = &new XR_LocStor(&$dbc, $config); + +$methods = array( + 'test' => 'Tests toupper and checks sessid, params: teststring, sessid.', + 'authenticate' => 'Checks authentication.', + 'login' => 'Login to storage.', + 'logout' => 'Logout from storage.', + 'existsAudioClip' => 'Checks if an Audio clip with the specified id is stored in local storage.', + 'storeAudioClip' => 'Store a new audio clip or replace an existing one.', + 'deleteAudioClip' => 'Delete an existing Audio clip.', + 'updateAudioClipMetadata' => 'Update the metadata of an Audio clip stored in Local storage.', + 'searchMetadata' => 'Search through the metadata of stored AudioClips, and return all matching clip ids.', + 'accessRawAudioData' => 'Get access to raw audio data of an AudioClip.', + 'releaseRawAudioData' => 'Release access for raw audio data.', + 'getAudioClip' => 'Return the contents of an Audio clip.' +); + +$defs = array(); +foreach($methods as $method=>$description){ + $defs["locstor.$method"] = array( + "function" => array(&$locStor, "xr_$method"), + "signature" => array(array($xmlrpcStruct, $xmlrpcStruct)), + "docstring" => $description + ); +} +$s=new xmlrpc_server( $defs ); +?> \ No newline at end of file diff --git a/livesupport/modules/storageServer/var/xmlrpc/xr_cli_test.py b/livesupport/modules/storageServer/var/xmlrpc/xr_cli_test.py new file mode 100755 index 000000000..30b9f6b26 --- /dev/null +++ b/livesupport/modules/storageServer/var/xmlrpc/xr_cli_test.py @@ -0,0 +1,112 @@ +#!/usr/bin/python + +#------------------------------------------------------------------------------ +# +# Copyright (c) 2004 Media Development Loan Fund +# +# This file is part of the LiveSupport project. +# http://livesupport.campware.org/ +# To report bugs, send an e-mail to bugs@campware.org +# +# LiveSupport is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# LiveSupport is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LiveSupport; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# +# Author : $Author: tomas $ +# Version : $Revision: 1.1 $ +# Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/storageServer/var/xmlrpc/Attic/xr_cli_test.py,v $ +# +#------------------------------------------------------------------------------ + +from xmlrpclib import * +import sys + +if len(sys.argv)<3: + print """ + Usage: xr_cli_pok.py [-v] [-s http:////xmlrpc/xrGreenBox.php] + commands and args: + listMethods + methodHelp + methodSignature + test + login + authenticate + logout + existsAudioClip + storeAudioClip + deleteAudioClip + updateAudioClipMetadata + searchMetadata + accessRawAudioData + releaseRawAudioData + getAudioClip +""" + sys.exit(1) + +pars = sys.argv +verbose=0 +if pars[1]=="-v": + pars.pop(1) + verbose=1 +if pars[1]=="-s": + pars.pop(1) + serverPath = pars.pop(1) +else: + serverPath = 'http://localhost:80/storage/xmlrpc/xrLocStor.php' +server = Server(serverPath) +method = pars.pop(1) +pars.pop(0) +if verbose: + print "method: "+method + print "pars: " + print pars + print "result:" +#sys.exit(0) + +try: + if method=="listMethods": + print server.system.listMethods() + elif method=="methodHelp": + print server.system.methodHelp(pars[0]) + elif method=="methodSignature": + print server.system.methodSignature(pars[0]) + elif method=="test": + print server.locstor.test({'sessid':pars[0], 'teststring':pars[1]}) + elif method=="authenticate": + print server.locstor.authenticate({'login':pars[0], 'pass':pars[1]}) + elif method=="login": + print server.locstor.login({'login':pars[0], 'pass':pars[1]}) + elif method=="logout": + print server.locstor.logout({'sessid':pars[0]}) + elif method=="existsAudioClip": + print server.locstor.existsAudioClip({'sessid':pars[0], 'gunid':pars[1]} ) + elif method=="storeAudioClip": + print server.locstor.storeAudioClip({'sessid':pars[0], 'gunid':pars[1], 'mediaFileLP':pars[2], 'mdataFileLP':pars[3]}) + elif method=="deleteAudioClip": + print server.locstor.deleteAudioClip({'sessid':pars[0], 'gunid':pars[1]}) + elif method=="updateAudioClipMetadata": + print server.locstor.updateAudioClipMetadata({'sessid':pars[0], 'gunid':pars[1], 'mdataFileLP':pars[2]}) + elif method=="searchMetadata": + print server.locstor.searchMetadata({'sessid':pars[0], 'criteria':pars[1]}) + elif method=="accessRawAudioData": + print server.locstor.accessRawAudioData({'sessid':pars[0], 'gunid':pars[1]}) + elif method=="releaseRawAudioData": + print server.locstor.releaseRawAudioData({'sessid':pars[0], 'tmpLink':pars[1]}) + elif method=="getAudioClip": + print server.locstor.getAudioClip({'sessid':pars[0], 'gunid':pars[1]}) + else: + print "Unknown command: "+method + sys.exit(1) +except Error, v: + print "XML-RPC Error:",v