eZ Publish  [4.0]
ezdbfilehandler.php
Go to the documentation of this file.
00001 <?php
00002 //
00003 // Definition of eZDBFileHandler class
00004 //
00005 // Created on: <19-Apr-2006 16:01:30 vs>
00006 //
00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00008 // SOFTWARE NAME: eZ Publish
00009 // SOFTWARE RELEASE: 4.0.x
00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS
00011 // SOFTWARE LICENSE: GNU General Public License v2.0
00012 // NOTICE: >
00013 //   This program is free software; you can redistribute it and/or
00014 //   modify it under the terms of version 2.0  of the GNU General
00015 //   Public License as published by the Free Software Foundation.
00016 //
00017 //   This program is distributed in the hope that it will be useful,
00018 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 //   GNU General Public License for more details.
00021 //
00022 //   You should have received a copy of version 2.0 of the GNU General
00023 //   Public License along with this program; if not, write to the Free
00024 //   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00025 //   MA 02110-1301, USA.
00026 //
00027 //
00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
00029 //
00030 
00031 /*! \file ezdbfilehandler.php
00032 
00033   Note: Not all code is using this class for cluster access, see index_image_mysql.php and index_image_pgsql.php for more custom code.
00034 */
00035 
00036 require_once( 'lib/ezutils/classes/ezdebugsetting.php' );
00037 require_once( 'lib/ezutils/classes/ezdebug.php' );
00038 
00039 class eZDBFileHandler
00040 {
00041     /*!
00042      Controls whether file data from database is cached on the local filesystem.
00043      \note This is primarily available for debugging purposes.
00044      */
00045     const LOCAL_CACHE = 1;
00046 
00047     /*!
00048      Controls the maximum number of metdata entries to keep in memory for this request.
00049      If the limit is reached the least used entries are removed.
00050      */
00051     const INFOCACHE_MAX = 200;
00052 
00053     /**
00054      * Constructor.
00055      *
00056      * $filePath File path. If specified, file metadata is fetched in the constructor.
00057      */
00058     function eZDBFileHandler( $filePath = false )
00059     {
00060         $filePath = eZDBFileHandler::cleanPath( $filePath );
00061         eZDebugSetting::writeDebug( 'kernel-clustering', "db::ctor( '$filePath' )" );
00062 
00063         // Init backend.
00064         if ( !isset( $GLOBALS['eZDBFileHandler_chosen_backend_class'] ) )
00065         {
00066             require_once( 'lib/ezutils/classes/ezini.php' );
00067             $fileINI = eZINI::instance( 'file.ini' );
00068             $backendName = 'mysql';
00069             if ( $fileINI->hasVariable( 'ClusteringSettings', 'DBBackend' ) )
00070                 $backendName = $fileINI->variable( 'ClusteringSettings', 'DBBackend' );
00071 
00072             require_once( 'kernel/classes/ezclusterfilehandler.php' );
00073             $searchPathArray = eZClusterFileHandler::searchPathArray();
00074 
00075             foreach ( $searchPathArray as $searchPath )
00076             {
00077                 $includeFileName = "$searchPath/dbbackends/$backendName.php";
00078                 if ( is_readable( $includeFileName ) )
00079                 {
00080                     include_once( $includeFileName );
00081                     $backendClassName = "eZDBFileHandler${backendName}Backend";
00082                     $GLOBALS['eZDBFileHandler_chosen_backend_class'] = $backendClassName;
00083                 }
00084             }
00085 
00086             if ( !isset( $GLOBALS['eZDBFileHandler_chosen_backend_class'] ) )
00087             {
00088                 eZDebug::writeError( "Cannot find ezdb cluster file backend: '$backendName'" );
00089                 return;
00090             }
00091         }
00092 
00093         $backendClassName = $GLOBALS['eZDBFileHandler_chosen_backend_class'];
00094         $this->backend = new $backendClassName;
00095         $this->backend->_connect( false );
00096         $this->backendVerify = null;
00097         $this->filePath = $filePath;
00098         $this->metaData = false;
00099 //        $this->metaData['name'] = $filePath;
00100 
00101 //        $this->loadMetaData();
00102     }
00103 
00104     /*!
00105      \public
00106      Load file meta information.
00107 
00108      \param $force File stats will be refreshed if true
00109     */
00110     function loadMetaData( $force = false )
00111     {
00112         // Fetch metadata.
00113         if ( $this->filePath === false )
00114             return;
00115 
00116         if ( $force && isset( $GLOBALS['eZClusterInfo'][$this->filePath] ) )
00117             unset( $GLOBALS['eZClusterInfo'][$this->filePath] );
00118 
00119         // Checks for metadata stored in memory, useful for repeated access
00120         // to the same file in one request
00121         // TODO: On PHP5 turn into static member
00122         if ( isset( $GLOBALS['eZClusterInfo'][$this->filePath] ) )
00123         {
00124             $GLOBALS['eZClusterInfo'][$this->filePath]['cnt'] += 1;
00125             $this->metaData = $GLOBALS['eZClusterInfo'][$this->filePath]['data'];
00126             return;
00127         }
00128 
00129         $metaData = $this->backend->_fetchMetadata( $this->filePath );
00130         if ( $metaData )
00131             $this->metaData = $metaData;
00132         else
00133             $this->metaData = false;
00134 
00135         // Clean up old entries if the maximum count is reached
00136         if ( isset( $GLOBALS['eZClusterInfo'] ) &&
00137              count( $GLOBALS['eZClusterInfo'] ) >= self::INFOCACHE_MAX )
00138         {
00139             usort( $GLOBALS['eZClusterInfo'],
00140                    create_function( '$a, $b',
00141                                     '$a=$a["cnt"]; $b=$b["cnt"]; if ( $a > $b ) return -1; else if ( $a == $b ) return 0; else return 1;' ) );
00142             array_pop( $GLOBALS['eZClusterInfo'] );
00143         }
00144         $GLOBALS['eZClusterInfo'][$this->filePath] = array( 'cnt' => 1,
00145                                                             'data' => $metaData );
00146     }
00147 
00148     /**
00149      * \public
00150      * \static
00151      * \param $filePath Path to the file being stored.
00152      * \param $scope    Means something like "file category". May be used to clean caches of a certain type.
00153      * \param $delete   true if the file should be deleted after storing.
00154      */
00155     function fileStore( $filePath, $scope = false, $delete = false, $datatype = false )
00156     {
00157         $filePath = eZDBFileHandler::cleanPath( $filePath );
00158         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileStore( '$filePath' )" );
00159 
00160         if ( $scope === false )
00161             $scope = 'UNKNOWN_SCOPE';
00162 
00163         if ( $datatype === false )
00164             $datatype = 'misc';
00165 
00166         $this->backend->_store( $filePath, $datatype, $scope );
00167 
00168         if ( $delete )
00169             @unlink( $filePath );
00170     }
00171 
00172     /**
00173      * Store file contents.
00174      *
00175      * \public
00176      * \static
00177      */
00178     function fileStoreContents( $filePath, $contents, $scope = false, $datatype = false )
00179     {
00180         $filePath = eZDBFileHandler::cleanPath( $filePath );
00181         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileStoreContents( '$filePath' )" );
00182 
00183         if ( $scope === false )
00184             $scope = 'UNKNOWN_SCOPE';
00185 
00186         if ( $datatype === false )
00187             $datatype = 'misc';
00188 
00189         $this->backend->_storeContents( $filePath, $contents, $scope, $datatype );
00190     }
00191 
00192     /**
00193      * Store file contents.
00194      *
00195      * \public
00196      *
00197      * \param $storeLocally If true the file will also be stored on the local file system.
00198      */
00199     function storeContents( $contents, $scope = false, $datatype = false, $storeLocally = false )
00200     {
00201         if ( $scope === false )
00202             $scope = 'UNKNOWN_SCOPE';
00203 
00204         if ( $datatype === false )
00205             $datatype = 'misc';
00206 
00207         $filePath = $this->filePath;
00208         eZDebugSetting::writeDebug( 'kernel-clustering', "db::storeContents( '$filePath' )" );
00209         $this->backend->_storeContents( $filePath, $contents, $scope, $datatype );
00210         if ( $storeLocally )
00211         {
00212             //include_once( 'lib/ezfile/classes/ezfile.php' );
00213             eZFile::create( basename( $filePath ), dirname( $filePath ), $contents, true );
00214         }
00215     }
00216 
00217     /**
00218      * Fetches file from db and saves it in FS under the same name.
00219      *
00220      * \public
00221      * \static
00222      */
00223     function fileFetch( $filePath )
00224     {
00225         $filePath = eZDBFileHandler::cleanPath( $filePath );
00226         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileFetch( '$filePath' )" );
00227 
00228         return $this->backend->_fetch( $filePath );
00229     }
00230 
00231     /*!
00232      Creates a single transaction out of the typical file operations for accessing caches.
00233      Caches are normally ready from the database or local file, if the entry does not exist
00234      or is expired then it generates the new cache data and stores it.
00235      This method takes care of these operations and handles the custom code by performing
00236      callbacks when needed.
00237 
00238      The $retrieveCallback is used when the file contents can be used (ie. not re-generation) and
00239      is called when the file is ready locally.
00240      The function will be called with the file path as the first parameter, the mtime as the second
00241      and optionally $extraData as the third.
00242      The function must return the file contents or an instance of eZClusterFileFailure which can
00243      be used to tell the system that the retrieve data cannot be used after all.
00244      $retrieveCallback can be set to null which makes the system go directly to the generation.
00245 
00246      The $generateCallback is used when the file content is expired or does not exist, in this
00247      case the content must be re-generated and stored.
00248      The function will be called with the file path as the first parameter and optionally $extraData
00249      as the second.
00250      The function must return an array with information on the contents, the array consists of:
00251      - scope    - The current scope of the file, is optional.
00252      - datatype - The current datatype of the file, is optional.
00253      - content  - The file content, this can be any type except null.
00254      - binarydata - The binary data which is written to the file.
00255      - store      - Whether *content* or *binarydata* should be stored to the file, if false it will simply return the data. Optional, by default it is true.
00256      Note: Set $generateCallback to false to disable generation callback.
00257      Note: Set $generateCallback to null to tell the function to perform a write lock but not do any generation, the generation must done be done by the caller by calling storeCache().
00258 
00259      Either *content* or *binarydata* must be supplied, if not an error is issued and it returns null.
00260      If *content* is set it will be used as the return value of this function, if not it will return the binary data.
00261      If *binarydata* is set it will be used as the binary data for the file, if not it will perform a var_export on *content* and use that as the binary data.
00262 
00263      For convenience the $generateCallback function can return a string which will be considered as the
00264      binary data for the file and returned as the content.
00265 
00266      For controlling how long a cache entry can be used the parameters $expiry and $ttl is used.
00267      $expiry can be set to a timestamp which controls the absolute max time for the cache, after this
00268      time/date the cache will never be used. If the value is set to a negative value or null there the
00269      expiration check is disabled.
00270 
00271      $ttl (time to live) tells how many seconds the cache can live from the time it was stored. If the
00272      value is set to negative or null there is no limit for the lifetime of the cache. A value of 0 means
00273      that the cache will always expire and practically disables caching.
00274      For the cache to be used both the $expiry and $ttl check must hold.
00275      */
00276     function processCache( $retrieveCallback, $generateCallback = null, $ttl = null, $expiry = null, $extraData = null )
00277     {
00278         //include_once( 'kernel/classes/ezclusterfilefailure.php' );
00279         $forceDB = false;
00280         $fname = $this->filePath;
00281         $timestamp = null;
00282         $curtime   = time();
00283         $tries     = 0;
00284 
00285         if ( $expiry < 0 )
00286             $expiry = null;
00287         if ( $ttl < 0 )
00288             $ttl = null;
00289 
00290         // Main loop
00291         while ( true )
00292         {
00293             // Start read checks
00294             // Note: The while loop is used to make it easier to break out of the "read" code
00295             while ( true )
00296             {
00297                 // No retrieve method so go directly to generate+store
00298                 if ( $retrieveCallback === null || !$fname )
00299                     break;
00300 
00301                 if ( !self::LOCAL_CACHE )
00302                 {
00303                     $forceDB = true;
00304                 }
00305                 else
00306                 {
00307                     $mtime = @filemtime( $fname );
00308 
00309                     if ( eZDBFileHandler::isExpired( $fname, $mtime, $expiry, $curtime, $ttl ) )
00310                     {
00311                         // Local file is older than global timestamp, check with DB
00312                         eZDebugSetting::writeDebug( 'kernel-clustering', "Local file (mtime=$mtime) is older than timestamp ($expiry) and ttl($ttl), check with DB" );
00313                         $forceDB = true;
00314                     }
00315                 }
00316 
00317                 if ( !$forceDB )
00318                 {
00319                     if ( $this->metaData === false )
00320                         $this->loadMetaData();
00321 
00322                     if ( $this->metaData === false || $this->metaData['mtime'] < 0 )
00323                     {
00324                         if ( $generateCallback !== false )
00325                             eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, need to regenerate data" );
00326                         else
00327                             eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, cannot get data" );
00328                         break;
00329                     }
00330 
00331                     if ( $this->metaData === false || eZDBFileHandler::isExpired( $fname, $mtime, $this->metaData['mtime'], $curtime, $ttl ) )
00332 //                      if ( $this->metaData === false || eZDBFileHandler::isExpired( $fname, $mtime, $expiry, $curtime, $ttl ) )
00333                     {
00334                         eZDebugSetting::writeDebug( 'kernel-clustering', "Local file (mtime=$mtime) is older than DB, checking with DB" );
00335                         $forceDB = true;
00336                     }
00337                     else
00338                     {
00339                         eZDebugSetting::writeDebug( 'kernel-clustering', "Processing local file $fname" );
00340                         $args = array( $fname, $mtime );
00341                         if ( $extraData !== null )
00342                             $args[] = $extraData;
00343                         $retval = call_user_func_array( $retrieveCallback, $args );
00344                         if ( $retval instanceof eZClusterFileFailure )
00345                         {
00346                             break;
00347                         }
00348                         return $retval;
00349                     }
00350                 }
00351 
00352                 if ( $this->metaData === false )
00353                     $this->loadMetaData();
00354 
00355                 if ( $this->metaData === false || $this->metaData['mtime'] < 0 )
00356                 {
00357                     if ( $generateCallback !== false )
00358                         eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, need to regenerate data" );
00359                     else
00360                         eZDebugSetting::writeDebug( 'kernel-clustering', "Database file is deleted, cannot get data" );
00361                     break;
00362                 }
00363 
00364                 if ( $this->metaData !== false && !eZDBFileHandler::isExpired( $fname, $this->metaData['mtime'], $expiry, $curtime, $ttl ) )
00365                 {
00366                     eZDebugSetting::writeDebug( 'kernel-clustering', "Callback from DB file $fname" );
00367                     if ( self::LOCAL_CACHE )
00368                     {
00369                         $this->fetch();
00370 
00371                         // Figure out which mtime to use for new file, must be larger than
00372                         // mtime in DB at least.
00373                         $mtime = $this->metaData['mtime'] + 1;
00374                         $localmtime = @filemtime( $fname );
00375                         $mtime = max( $mtime, $localmtime );
00376                         touch( $fname, $mtime, $mtime );
00377                         clearstatcache(); // Needed because of touch() call
00378 
00379                         $args = array( $fname, $mtime );
00380                         if ( $extraData !== null )
00381                             $args[] = $extraData;
00382                         $retval = call_user_func_array( $retrieveCallback, $args );
00383                         if ( $retval instanceof eZClusterFileFailure )
00384                         {
00385                             break;
00386                         }
00387                         return $retval;
00388                     }
00389                     else
00390                     {
00391                         $uniquePath = $this->fetchUnique();
00392 
00393                         $args = array( $fname, $this->metaData['mtime'] );
00394                         if ( $extraData !== null )
00395                             $args[] = $extraData;
00396                         $retval = call_user_func_array( $retrieveCallback, $args );
00397                         $this->fileDeleteLocal( $uniquePath );
00398                         if ( $retval instanceof eZClusterFileFailure )
00399                             break;
00400                         return $retval;
00401                     }
00402                 }
00403                 eZDebugSetting::writeDebug( 'kernel-clustering', "Database file does not exist, need to regenerate data" );
00404                 break;
00405             }
00406 
00407             if ( $tries >= 2 )
00408             {
00409                 eZDebugSetting::writeDebug( 'kernel-clustering', "Reading was retried $tries times and reached the maximum, returning null" );
00410                 return null;
00411             }
00412 
00413             // Generation part starts here
00414             if ( isset( $retval ) &&
00415                  $retval instanceof eZClusterFileFailure )
00416             {
00417                 if ( $retval->errno() != 1 ) // check for non-expiry error codes
00418                 {
00419                     eZDebug::writeError( "Failed to retrieve data from callback", 'eZDBFileHandler::processCache' );
00420                     return null;
00421                 }
00422                 $message = $retval->message();
00423                 if ( strlen( $message ) > 0 )
00424                 {
00425                     eZDebugSetting::writeDebug( 'kernel-clustering', $retval->message(), "eZClusterFileFailure::processCache" );
00426                 }
00427                 // the retrieved data was expired so we need to generate it, let's continue
00428             }
00429 
00430             // We need to lock if we have a generate-callback or
00431             // the generation is deferred to the caller.
00432             // Note: false means no generation
00433             if ( $generateCallback !== false && $fname )
00434             {
00435                 // Lock the entry for exclusive access, if the entry does not exist
00436                 // it will be inserted with mtime=-1
00437                 $res = $this->backend->_exclusiveLock( $fname, 'processCache' );
00438                 if ( !$res || $res instanceof eZMySQLBackendError )
00439                 {
00440                     // Cannot get exclusive lock, so return null.
00441                     return null;
00442                 }
00443 
00444                 // some db backends have advisory locking and we use a kind of
00445                 // two-phase locking mechanism:
00446                 // lock is first created, then we check if it was acquired or
00447                 // if some other process has written the file in the meantime
00448                 if ( !$this->backend->_verifyExclusiveLock( $fname, $expiry, $curtime, $ttl, 'processCache' ) )
00449                 {
00450                     eZDebugSetting::writeDebug( 'kernel-clustering', "Database file was generated while we were locked, using that instead" );
00451                     $this->backend->_rollback( 'processCache' ); // release lock
00452                     $this->metaData = false;
00453                     unset( $GLOBALS['eZClusterInfo'][$fname] );
00454                     ++$tries;
00455                     continue; // retry reading file
00456                 }
00457             }
00458 
00459             // File in DB is outdated or non-existing, call write-callback to generate content
00460             if ( $generateCallback )
00461             {
00462                 $args = array( $fname );
00463                 if ( $extraData !== null )
00464                     $args[] = $extraData;
00465                 $fileData = call_user_func_array( $generateCallback, $args );
00466                 return $this->storeCache( $fileData );
00467             }
00468 
00469             break;
00470         }
00471 
00472         return new eZClusterFileFailure( 2, "Manual generation of file data is required, calling storeCache is required" );
00473     }
00474 
00475     /*!
00476      \static
00477      \private
00478      Calculates if the file data is expired or not.
00479 
00480      \param $fname Name of file, available for easy debugging.
00481      \param $mtime Modification time of file, can be set to false if file does not exist.
00482      \param $expiry Time when file is to be expired, a value of -1 will disable this check.
00483      \param $curtime The current time to check against.
00484      \param $ttl Number of seconds the data can live, set to null to disable TTL.
00485      */
00486     static function isExpired( $fname, $mtime, $expiry, $curtime, $ttl )
00487     {
00488         if ( $mtime == false )
00489         {
00490             return true;
00491         }
00492         else if ( $ttl === null )
00493         {
00494             $ret = $mtime < $expiry;
00495             return $ret;
00496         }
00497         else
00498         {
00499             $ret = $mtime < max( $expiry, $curtime - $ttl );
00500             return $ret;
00501         }
00502     }
00503 
00504     /*!
00505      \private
00506      Stores the data in $fileData to the remote and local file and commits the transaction.
00507 
00508      The parameter $fileData must contain the same as information as the $generateCallback returns as explained in processCache().
00509 
00510      \note This method is just a continuation of the code in processCache() and is not meant to be called alone since it relies on specific state in the database.
00511      */
00512     function storeCache( $fileData )
00513     {
00514         $fname = $this->filePath;
00515 
00516         $scope       = false;
00517         $datatype    = false;
00518         $binaryData  = null;
00519         $fileContent = null;
00520         $store       = true;
00521         if ( is_array( $fileData ) )
00522         {
00523             if ( isset( $fileData['scope'] ) )
00524                 $scope = $fileData['scope'];
00525             if ( isset( $fileData['datatype'] ) )
00526                 $datatype = $fileData['datatype'];
00527             if ( isset( $fileData['content'] ) )
00528                 $fileContent = $fileData['content'];
00529             if ( isset( $fileData['binarydata'] ) )
00530                 $binaryData = $fileData['binarydata'];
00531             if ( isset( $fileData['store'] ) )
00532                 $store = $fileData['store'];
00533         }
00534         else
00535             $binaryData = $fileData;
00536 
00537         $mtime = false;
00538         $result = null;
00539         if ( $binaryData === null &&
00540              $fileContent === null )
00541         {
00542             eZDebug::writeError( "Write callback need to set the 'content' or 'binarydata' entry" );
00543             $this->backend->_rollback( 'storeCache' );
00544             return null;
00545         }
00546 
00547         if ( $binaryData === null )
00548             $binaryData = "<" . "?php\n\treturn ". var_export( $fileContent, true ) . ";\n?" . ">\n";
00549 
00550         if ( $fileContent === null )
00551             $result = $binaryData;
00552         else
00553             $result = $fileContent;
00554 
00555         if ( !$fname )
00556             return $result;
00557 
00558         // Check if we are allowed to store the data, if not just return the result
00559         if ( !$store )
00560         {
00561             $this->backend->_rollback( 'storeCache' );
00562             return $result;
00563         }
00564 
00565         if ( self::LOCAL_CACHE )
00566         {
00567             // Store content also locally
00568             eZDebugSetting::writeDebug( 'kernel-clustering', "Writing new file content to local file $fname" );
00569             //include_once( 'lib/ezfile/classes/ezfile.php' );
00570             eZFile::create( basename( $fname ), dirname( $fname ), $binaryData, true );
00571             $mtime = @filemtime( $fname );
00572         }
00573 
00574         eZDebugSetting::writeDebug( 'kernel-clustering', "Writing new file content to database for $fname" );
00575         $this->storeContents( $binaryData, $scope, $datatype, $mtime );
00576 
00577         $this->backend->_freeExclusiveLock( 'storeCache' );
00578 
00579         return $result;
00580     }
00581 
00582     /*!
00583      Provides access to the file contents by downloading the file locally and
00584      calling $callback with the local filename. The callback can then process the
00585      contents and return the data in the same way as in processCache().
00586      Downloading is only done once so the local copy is kept, while updates to the
00587      remote DB entry is synced with the local one.
00588 
00589      The parameters $expiry and $extraData is the same as for processCache().
00590 
00591      \note Unlike processCache() this returns null if the file cannot be accessed.
00592      */
00593     function processFile( $callback, $expiry = false, $extraData = null )
00594     {
00595         $result = $this->processCache( $callback, false, null, $expiry, $extraData );
00596         if ( $result instanceof eZClusterFileFailure )
00597         {
00598             return null;
00599         }
00600         return $result;
00601     }
00602 
00603     /**
00604      * Fetches file from db and saves it in FS under unique name.
00605      *
00606      * \public
00607      * \static
00608      * \return filename with path of a saved file. You can use this filename to get contents of file from filesystem.
00609      */
00610     function fetchUnique( )
00611     {
00612         $filePath = $this->filePath;
00613         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fetchUnique( '$filePath' )" );
00614 
00615         $fetchedFilePath = $this->backend->_fetch( $filePath, true );
00616         $this->uniqueName = $fetchedFilePath;
00617         return $fetchedFilePath;
00618     }
00619 
00620     /**
00621      * Fetches file from db and saves it in FS under the same name.
00622      *
00623      * \public
00624      */
00625     function fetch( $cacheLocally = false )
00626     {
00627         $filePath = $this->filePath;
00628         $metaData = $this->backend->_fetchMetadata( $filePath );
00629         $mtime = @filemtime( $filePath );
00630         if ( !$cacheLocally ||
00631              $metaData === false ||
00632              $mtime === false ||
00633              $mtime < $metaData['mtime'] ||
00634              @filesize( $filePath ) != $metaData['size'] ||
00635              !is_readable( $filePath ) )
00636         {
00637             eZDebugSetting::writeDebug( 'kernel-clustering', "db::fetch( '$filePath' )" );
00638             $this->backend->_fetch( $filePath );
00639         }
00640     }
00641 
00642     /**
00643      * Returns file contents.
00644      *
00645      * \public
00646      * \static
00647      * \return contents string, or false in case of an error.
00648      */
00649     function fileFetchContents( $filePath )
00650     {
00651         $filePath = eZDBFileHandler::cleanPath( $filePath );
00652         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileFetchContents( '$filePath' )" );
00653 
00654         $contents = $this->backend->_fetchContents( $filePath );
00655         return $contents;
00656     }
00657 
00658     /**
00659      * Returns file contents.
00660      *
00661      * \public
00662      * \return contents string, or false in case of an error.
00663      */
00664     function fetchContents()
00665     {
00666         $filePath = $this->filePath;
00667         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileFetchContents( '$filePath' )" );
00668         $contents = $this->backend->_fetchContents( $filePath );
00669         return $contents;
00670     }
00671 
00672     /**
00673      * Returns file metadata.
00674      *
00675      * \public
00676      */
00677     function stat()
00678     {
00679         eZDebugSetting::writeDebug( 'kernel-clustering', "db::stat()" );
00680         if ( $this->metaData === false )
00681             $this->loadMetaData();
00682         return $this->metaData;
00683     }
00684 
00685     /**
00686      * Returns file size.
00687      *
00688      * \public
00689      */
00690     function size()
00691     {
00692         eZDebugSetting::writeDebug( 'kernel-clustering', "db::size()" );
00693 
00694         if ( $this->metaData === false )
00695             $this->loadMetaData();
00696         return isset( $this->metaData['size'] ) ? $this->metaData['size'] : null;
00697     }
00698 
00699     /**
00700      * Returns file modification time.
00701      *
00702      * \public
00703      */
00704     function mtime()
00705     {
00706         eZDebugSetting::writeDebug( 'kernel-clustering', "db::mtime()" );
00707 
00708         if ( $this->metaData === false )
00709             $this->loadMetaData();
00710         return isset( $this->metaData['mtime'] ) ? $this->metaData['mtime'] : null;
00711     }
00712 
00713     /**
00714      * Returns file name.
00715      *
00716      * \public
00717      */
00718     function name()
00719     {
00720         eZDebugSetting::writeDebug( 'kernel-clustering', "db::name()" );
00721 
00722         return $this->filePath;
00723     }
00724 
00725     /**
00726      * \public
00727      * \static
00728      * \sa fileDeleteByWildcard()
00729      */
00730     function fileDeleteByRegex( $dir, $fileRegex )
00731     {
00732         $dir = eZDBFileHandler::cleanPath( $dir );
00733         $fileRegex = eZDBFileHandler::cleanPath( $fileRegex );
00734         eZDebug::writeWarning( "Using eZDBFileHandler::fileDeleteByRegex is not recommended since it has some severe performance issues" );
00735         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileDeleteByRegex( '$dir', '$fileRegex' )" );
00736 
00737         $regex = '^' . ( $dir ? $dir . '/' : '' ) . $fileRegex;
00738         $this->backend->_deleteByRegex( $regex );
00739     }
00740 
00741     /**
00742      * \public
00743      * \static
00744      * \sa fileDeleteByRegex()
00745      */
00746     function fileDeleteByWildcard( $wildcard )
00747     {
00748         $wildcard = eZDBFileHandler::cleanPath( $wildcard );
00749         eZDebug::writeWarning( "Using eZDBFileHandler::fileDeleteByWildcard is not recommended since it has some severe performance issues" );
00750         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileDeleteByWildcard( '$wildcard' )" );
00751 
00752         $this->backend->_deleteByWildcard( $wildcard );
00753     }
00754 
00755     /**
00756      * \public
00757      * \static
00758      */
00759     function fileDeleteByDirList( $dirList, $commonPath, $commonSuffix )
00760     {
00761         foreach ( $dirList as $key => $dirItem )
00762         {
00763             $dirList[$key] = eZDBFileHandler::cleanPath( $dirItem );
00764 
00765         }
00766         $commonPath = eZDBFileHandler::cleanPath( $commonPath );
00767         $commonSuffix = eZDBFileHandler::cleanPath( $commonSuffix );
00768         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileDeleteByDirList( '$dirList', '$commonPath', '$commonSuffix' )" );
00769 
00770         $this->backend->_deleteByDirList( $dirList, $commonPath, $commonSuffix );
00771     }
00772 
00773     /**
00774      * Deletes specified file/directory.
00775      *
00776      * If a directory specified it is deleted recursively.
00777      *
00778      * \public
00779      * \static
00780      */
00781     function fileDelete( $path, $fnamePart = false )
00782     {
00783         $path = eZDBFileHandler::cleanPath( $path );
00784         $fnamePart = eZDBFileHandler::cleanPath( $fnamePart );
00785         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileDelete( '$path' )" );
00786 
00787         if ( $fnamePart === false )
00788             $this->backend->_delete( $path );
00789         if ( $fnamePart !== false )
00790             $pattern = $path . '/' . $fnamePart . '%';
00791         else
00792             $pattern = $path . '/%';
00793         $this->backend->_deleteByLike( $pattern );
00794     }
00795 
00796     /**
00797      * Deletes specified file/directory.
00798      *
00799      * If a directory specified it is deleted recursively.
00800      *
00801      * \public
00802      * \static
00803      */
00804     function delete()
00805     {
00806         $path = $this->filePath;
00807         eZDebugSetting::writeDebug( 'kernel-clustering', "db::delete( '$path' )" );
00808 
00809         $this->backend->_delete( $path );
00810         $this->backend->_deleteByLike( $path . '/%' );
00811 
00812         $this->metaData = false;
00813     }
00814 
00815     /**
00816      * Deletes a file that has been fetched before.
00817      *
00818      * \public
00819      * \static
00820      */
00821     function fileDeleteLocal( $path )
00822     {
00823         $path = eZDBFileHandler::cleanPath( $path );
00824         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileDeleteLocal( '$path' )" );
00825 
00826         @unlink( $path );
00827     }
00828 
00829     /**
00830      * Deletes a file that has been fetched before.
00831      *
00832      * \public
00833      */
00834     function deleteLocal()
00835     {
00836         $path = $this->filePath;
00837         eZDebugSetting::writeDebug( 'kernel-clustering', "db::deleteLocal( '$path' )" );
00838         @unlink( $path );
00839     }
00840 
00841     /*!
00842      Purge local and remote file data for current file.
00843      */
00844     function purge( $printCallback = false, $microsleep = false, $max = false, $expiry = false )
00845     {
00846         $file = $this->filePath;
00847         if ( $max === false )
00848             $max = 100;
00849         $count = 0;
00850         do
00851         {
00852             if ( $count > 0 && $microsleep )
00853                 usleep( $microsleep ); // Sleep a bit to make the database happier
00854             $count = $this->backend->_purgeByLike( $file . "/%", true, $max, $expiry, 'purge' );
00855             $this->backend->_purge( $file, true, $expiry, 'purge' );
00856             if ( $printCallback )
00857                 call_user_func_array( $printCallback,
00858                                       array( $file, $count ) );
00859         } while ( $count > 0 );
00860         // Remove local copy
00861         if ( is_file( $file ) )
00862         {
00863             @unlink( $file );
00864         }
00865         else if ( is_dir( $file ) )
00866         {
00867             //include_once( 'lib/ezfile/classes/ezdir.php' );
00868             eZDir::recursiveDelete( $file );
00869         }
00870     }
00871 
00872     /**
00873      * Check if given file/dir exists.
00874      *
00875      * \public
00876      * \static
00877      */
00878     function fileExists( $path )
00879     {
00880         $path = eZDBFileHandler::cleanPath( $path );
00881         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileExists( '$path' )" );
00882 
00883         $rc = $this->backend->_exists( $path );
00884         return $rc;
00885     }
00886 
00887     /**
00888      * Check if given file/dir exists.
00889      *
00890      * NOTE: this function does not interact with database.
00891      * Instead, it just returns existance status determined in the constructor.
00892      *
00893      * \public
00894      */
00895     function exists()
00896     {
00897         $path = $this->filePath;
00898         eZDebugSetting::writeDebug( 'kernel-clustering', "db::exists( '$path' )" );
00899         $rc = $this->backend->_exists( $path );
00900         return $rc;
00901     }
00902 
00903     /**
00904      * Outputs file contents prepending them with appropriate HTTP headers.
00905      *
00906      * \public
00907      * \deprecated This function should not be used since it cannot handle reading errors.
00908      *             For the PHP 5 port this should be removed.
00909      */
00910     function passthrough()
00911     {
00912         $path = $this->filePath;
00913         eZDebugSetting::writeDebug( 'kernel-clustering', "db::passthrough( '$path' )" );
00914         if ( $this->metaData === false )
00915             $this->loadMetaData();
00916         $size = $this->metaData['size'];
00917         $mimeType = $this->metaData['datatype'];
00918         $mtime = $this->metaData['mtime'];
00919         $mdate = gmdate( 'D, d M Y H:i:s T', $mtime );
00920 
00921         header( "Content-Length: $size" );
00922         header( "Content-Type: $mimeType" );
00923         header( "Last-Modified: $mdate GMT" );
00924         header( "Expires: ". gmdate('D, d M Y H:i:s', time() + 6000) . ' GMT');
00925         header( "Connection: close" );
00926         header( "X-Powered-By: eZ Publish" );
00927         header( "Accept-Ranges: bytes" );
00928 
00929         $this->backend->_passThrough( $path );
00930     }
00931 
00932     /**
00933      * Copy file.
00934      *
00935      * \public
00936      * \static
00937      */
00938     function fileCopy( $srcPath, $dstPath )
00939     {
00940         $srcPath = eZDBFileHandler::cleanPath( $srcPath );
00941         $dstPath = eZDBFileHandler::cleanPath( $dstPath );
00942         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileCopy( '$srcPath', '$dstPath' )" );
00943 
00944         $this->backend->_copy( $srcPath, $dstPath );
00945     }
00946 
00947     /**
00948      * Create symbolic or hard link to file.
00949      *
00950      * \public
00951      * \static
00952      */
00953     function fileLinkCopy( $srcPath, $dstPath, $symLink )
00954     {
00955         $srcPath = eZDBFileHandler::cleanPath( $srcPath );
00956         $dstPath = eZDBFileHandler::cleanPath( $dstPath );
00957         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileLinkCopy( '$srcPath', '$dstPath' )" );
00958 
00959         $this->backend->_linkCopy( $srcPath, $dstPath );
00960     }
00961 
00962     /**
00963      * Move file.
00964      *
00965      * \public
00966      * \static
00967      */
00968     function fileMove( $srcPath, $dstPath )
00969     {
00970         $srcPath = eZDBFileHandler::cleanPath( $srcPath );
00971         $dstPath = eZDBFileHandler::cleanPath( $dstPath );
00972         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileMove( '$srcPath', '$dstPath' )" );
00973 
00974         $this->backend->_rename( $srcPath, $dstPath );
00975 
00976         $this->metaData = false;
00977     }
00978 
00979     /**
00980      * Move file.
00981      *
00982      * \public
00983      */
00984     function move( $dstPath )
00985     {
00986         $dstPath = eZDBFileHandler::cleanPath( $dstPath );
00987         $srcPath = $this->filePath;
00988 
00989         eZDebugSetting::writeDebug( 'kernel-clustering', "db::fileMove( '$srcPath', '$dstPath' )" );
00990 
00991         $this->backend->_rename( $srcPath, $dstPath );
00992 
00993         $this->metaData = false;
00994     }
00995 
00996     /**
00997      * Get list of files stored in database.
00998      *
00999      * Used in bin/php/clusterize.php.
01000      */
01001     function getFileList( $skipBinaryFiles = false, $skipImages = false )
01002     {
01003         eZDebugSetting::writeDebug( 'kernel-clustering',
01004                                     sprintf( "db::getFileList( %d, %d )",
01005                                               (int) $skipBinaryFiles, (int) $skipImages ) );
01006         return $this->backend->_getFileList( $skipBinaryFiles, $skipImages );
01007     }
01008 
01009     /*!
01010      \static
01011      Returns a clean version of input $path.
01012 
01013      - Backslashes are turned into slashes.
01014      - Multiple consecutive slashes are turned into one slash.
01015      - Ending slashes are removed.
01016 
01017      \example
01018      my\windows\path => my/windows/path
01019      extra//slashes/\are/fixed => extra/slashes/are/fixed
01020      ending/slashes/ => ending/slashes
01021      \endexample
01022      */
01023     static function cleanPath( $path )
01024     {
01025         if ( !is_string( $path ) )
01026             return $path;
01027         return preg_replace( array( "#[/\\\\]+#", "#/$#" ),
01028                              array( "/",        "" ),
01029                              $path );
01030     }
01031 
01032     /// vars
01033     public $backend;
01034     public $backendVerify;
01035     public $filePath;
01036     public $metaData;
01037 }
01038 
01039 ?>