-<?php
-/*=====================================================================*\
-|| ###################################################################
-|| # ViewSVN [#]version[#]
-|| # Copyright ©2002-[#]year[#] Iris Studios, Inc.
-|| #
-|| # This program is free software; you can redistribute it and/or modify
-|| # it under the terms of the GNU General Public License as published by
-|| # the Free Software Foundation; version [#]gpl[#] of the License.
-|| #
-|| # This program is distributed in the hope that it will be useful, but
-|| # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-|| # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-|| # more details.
-|| #
-|| # You should have received a copy of the GNU General Public License along
-|| # with this program; if not, write to the Free Software Foundation, Inc.,
-|| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-|| ###################################################################
-\*=====================================================================*/
-
-/**
-* File container for cacheV class
-*
-* @package ViewSVN
-*/
-
-/**
-* cacheV
-*
-* This class is responsible for interacting with a given cacheV table.
-* It controls rebuilding from scratch, updates, and querying the cache.
-*
-* @author Iris Studios, Inc.
-* @copyright Copyright ©2002 - [#]year[#], Iris Studios, Inc.
-* @version $Revision$
-* @package ViewSVN
-*
-*/
-class cacheV
-{
- /**
- * Controller
- * @var object
- * @access private
- */
- var $controller = null;
-
- /**
- * cacheV hash
- * @var string
- * @access private
- */
- var $hash;
-
- /**
- * Record count - the number of records in cacheV
- * @var integer
- * @access private
- */
- var $count;
-
- /**
- * Memcache for all fetched revisions so we don't have to query-dupe
- * @var array
- * @access private
- */
- var $memcache = array('revs' => array(), 'nodes' => array());
-
- // ###################################################################
- /**
- * Constructor: initialies the registry
- *
- * @param object Controller
- */
- function cacheV(&$controller)
- {
- $this->controller =& $controller;
- $this->set_hash();
- }
-
- // ###################################################################
- /**
- * Sets the hash so we know what table we're dealing with
- *
- * @access public
- */
- function set_hash()
- {
- $this->hash = md5($this->controller->repospath);
- $this->controller->registry->debug("hash: $this->hash");
- }
-
- // ###################################################################
- /**
- * Returns a node string that has the beginning and ending slashes
- * removed to allow it to match to the _nodes cacheV table
- *
- * @access public
- *
- * @param string Original string
- *
- * @return string Matchable string
- */
- function fetch_node_string($node)
- {
- trigger_error('You shouldn\'t be calling fetch_node_string. It\'s evil.', E_USER_WARNING);
- return preg_replace('#(^/|/$)#', '', $node);
- }
-
- // ###################################################################
- /**
- * Returns a specific log entry
- *
- * @access public
- *
- * @param integer Revision number
- *
- * @return array Complete revision/commit entry
- */
- function fetch_revision($revision)
- {
- $revision = $this->controller->registry->clean($revision, TYPE_UINT);
-
- if (!isset($this->memcache['revs']["$revision"]))
- {
- $this->memcache['revs']["$revision"] = $this->controller->registry->db->query_first("SELECT * FROM c{$this->hash}_revs " . ($revision == 0 ? " ORDER BY revision DESC LIMIT 1" : "WHERE revision = $revision"));
- $this->memcache['revs']["$revision"]['files'] = unserialize($this->memcache['revs']["$revision"]['files']);
- }
-
- return $this->memcache['revs']["$revision"];
- }
-
- // ###################################################################
- /**
- * Returns the revision entry before the specified one
- *
- * @access public
- *
- * @param integer Revision number
- *
- * @return array Complete revision/commit entry
- */
- function fetch_prev_revision($revision)
- {
- $data = $this->fetch_node();
- $data = $data['history'];
- if (sizeof($data) < 1)
- {
- return $this->fetch_revision(0);
- }
-
- $list = array_keys($data);
- $key = array_search($revision, $list);
-
- if ($revision == 'HEAD')
- {
- $key = 0;
- }
-
- $key++; // go to the next earliest revision
- if (!isset($list["$key"]))
- {
- return -1;
- }
-
- return $this->fetch_revision($list["$key"]);
- }
-
- // ###################################################################
- /**
- * Returns the latest revision that the file is at
- *
- * @access public
- *
- * @return integer HEAD revision
- */
- function fetch_head_revision()
- {
- $data = $this->fetch_node();
- $data = $data['history'];
-
- if (is_array($data))
- {
- return max(array_keys($data));
- }
- else
- {
- return 0;
- }
- }
-
- // ###################################################################
- /**
- * Returns the revision entry that it's in context with the node. For
- * instance, if the version 50 was passed and this node only has 48 as
- * it's max, 48 will be returned.
- *
- * @access public
- *
- * @param integer Target revision
- *
- * @return integer Contextual revision
- */
- function fetch_revision_context($target)
- {
- $data = $this->fetch_node();
- $data = $data['history'];
-
- if ($target == 'HEAD')
- {
- $target = $this->fetch_head_revision();
- }
-
- if (isset($data["$target"]))
- {
- return $this->fetch_revision($target);
- }
-
- if (is_array($data))
- {
- $keys = array_keys($data);
- }
- else
- {
- return 0;
- }
-
- $prev = 0;
- foreach ($keys AS $id => $revnum)
- {
- if ($target > $revnum)
- {
- $prev = $revnum;
- if ($prev > $rev)
- {
- $rev = $prev;
- }
- }
- else if ($target < $revnum)
- {
- $rev = $prev;
- }
- else
- {
- $rev = $keys[0];
- }
- }
-
- return $this->fetch_revision($rev);
- }
-
- // ###################################################################
- /**
- * Fetches the latest revision for a given path
- *
- * @access public
- *
- * @return integer Latest revision; FALSE if none (not in HEAD)
- */
- function fetch_node()
- {
- $node = $this->controller->path;
- if (!isset($this->memcache['nodes']["$node"]))
- {
- $result = $this->controller->registry->db->query_first("SELECT * FROM c{$this->hash}_nodes WHERE name = '" . $this->controller->registry->escape($node) . "'");
- if ($result == false)
- {
- return false;
- }
-
- $this->memcache['nodes']["$node"] = $result;
- $this->memcache['nodes']["$node"]['history'] = unserialize($this->memcache['nodes']["$node"]['history']);
- $this->memcache['nodes']["$node"]['properties'] = unserialize($this->memcache['nodes']["$node"]['properties']);
- }
-
- return $this->memcache['nodes']["$node"];
- }
-
- // ###################################################################
- /**
- * Checks to see if a given node is a directory. Returns TRUE if so.
- *
- * @access public
- *
- * @return bool TRUE if directory, FALSE if not
- */
- function isdir()
- {
- $node = $this->fetch_node();
- if ($node['node'] == 'dir')
- {
- return true;
- }
-
- return false;
- }
-
- // ###################################################################
- /**
- * Checks to see if it's necessary to rebuild the cacheV table for the
- * current repository. This is done by making sure $count > 0. If not,
- * then rebuild() is run. This also checks against the cacheV table
- * to make sure that it's up-to-date against the root repository.
- *
- * @access public
- *
- * @return bool Whether or not any part of the cache was (re-)built
- */
- function exec_build()
- {
- $result = $this->controller->registry->db->query_first("SELECT MAX(revision) AS max FROM c{$this->hash}_revs");
- $this->count = $result['max'];
-
- // time to go from the start
- if ($this->count == 0)
- {
- $this->build(null);
- }
- else
- {
- // send an Xquery to SVN to see if we need to update
- $query = $this->controller->library->svn('info --xml ' . $this->controller->repospath);
- $query = implode("\n", $query);
-
- $tree = $this->controller->registry->xml->parse($query);
-
- if ($tree['info']['entry']['revision'] != $this->count)
- {
- $this->build($this->count);
- return true;
- }
- }
-
- return false;
- }
-
- // ###################################################################
- /**
- * Builds the cacheV table. This can be used to build only part of the
- * cache or the entire thing, if the revision is set to NULL.
- *
- * @access public
- *
- * @param integer Lower (current) revision
- * @param bool Use separate and individual queries for inserting?
- */
- function build($revision, $seps = false)
- {
- $start = microtime();
-
- // get _revs
- $output = $this->controller->library->svn('log --xml -v ' . ($revision !== null ? '-r' . $revision . ':HEAD ' : '') . $this->controller->repospath);
- $output = implode("\n", $output);
- $tree = $this->controller->registry->xml->parse($output);
-
- // construct _revs inserts and the list of add revisions
- foreach ($tree['log']['logentry'] AS $log)
- {
- XML::unify_node($log['paths']['path']);
-
- $inserts['revs'][] = "($log[revision], '{$log['author']['value']}', '{$log['date']['value']}', '" . $this->controller->registry->escape($log['msg']['value']) . "', '" . $this->controller->registry->escape(serialize($log['paths']['path'])) . "')";
-
- foreach ($log['paths']['path'] AS $path)
- {
- if (trim($path['action']) == 'A')
- {
- $path['value'] = preg_replace('#^/#', '', $path['value']);
- $addlist["$path[value]"] = $log['revision'];
- }
-
- $filelist[] = $this->controller->repospath . $path['value'] . '@' . $log['revision'];
- }
- }
-
- $newfilelist = array();
- foreach ($filelist AS $item)
- {
-
- }
-
- // get _nodes
- $output = $this->controller->library->svn('info --xml ' . implode(' ', $filelist));
- $output = implode("\n", $output);
- $infolist = $this->controller->registry->xml->parse($output);
-
- // other part of _nodes: properties
- $output = $this->controller->library->svn('proplist -v ' . implode(' ', $filelist));
- $init = false;
- foreach ($output AS $line)
- {
- if (preg_match('#^Properties on \'(.*?)\':$#', $line, $bits))
- {
- if ($init == true)
- {
- $proplist["$index"]["$curprop"] = trim($proplist["$index"]["$curprop"]);
- }
- $init = true;
-
- $index = str_replace($this->controller->repospath, '', $bits[1]);
- $index = ($index == '' ? '/' : $index);
- $capture = false;
- }
- else
- {
- if (preg_match('#^\s+(.*)\s:\s(.*)#', $line, $matches))
- {
- $curprop = $matches[1];
- $proplist["$index"]["$curprop"] = $matches[2] . "\n";
- $capture = true;
- }
- else if ($capture == true)
- {
- $proplist["$index"]["$curprop"] .= $line . "\n";
- }
- }
- }
-
- XML::unify_node($infolist['info']['entry']);
-
- $nodeindex = 0;
- $nodesat = array();
- // construct list of HEAD nodes for _nodes
- foreach ($infolist['info']['entry'] AS $node)
- {
- $history = $this->controller->library->svn('log --xml ' . $node['url']['value']);
- $history = implode("\n", $history);
- $history = $this->controller->registry->xml->parse($history);
-
- $loglist = array();
- $latestrev = -1;
- foreach ($history['log']['logentry'] AS $log)
- {
- // WHY THE HELL DOES THIS GET HIT ON REBUILDS?
- if (!is_array($log))
- {
- /*print_r($node);
- var_dump($log);*/
- continue;
- }
- $loglist["$log[revision]"] = array(
- 'revision' => $log['revision'],
- 'author' => $log['author']['value'], // why does PHP5 hate this?
- 'date' => $log['date']['value'],
- 'message' => $log['msg']['value']
- );
- }
-
- $path = str_replace($this->controller->repospath, '', $node['url']['value']);
- $path = ($path == '' ? '/' : $path);
-
- $nodesat["$path"][ $node['commit']['revision'] ] = $nodeindex;
- $max = max(array_keys($nodesat["$path"]));
- if ($max < $node['commit']['revision'])
- {
- continue;
- }
- else if ($max >= $node['commit']['revision'])
- {
- foreach ($nodesat["$path"] AS $rev => $ind)
- {
- if ($rev != $node['commit']['revision'])
- {
- unset($inserts['nodes'][ $nodesat["$path"]["$rev"] ]);
- }
- }
- }
-
- $inserts['nodes']["$nodeindex"] = "('$path', '" . $node['kind'] . "', " . $node['commit']['revision'] . ", '" . $this->controller->registry->escape(serialize($loglist)) . "', '" . $this->controller->registry->escape(serialize($proplist["$path"])) . "')";
-
- $nodeindex++;
- }
-
- if ($seps == false)
- {
- // insert _revs
- var_dump("
- REPLACE INTO c{$this->hash}_revs
- (revision, author, dateline, message, files)
- VALUES
- " . implode(",\n", $inserts['revs'])
- );
-
- // insert _nodes
- var_dump("
- REPLACE INTO c{$this->hash}_nodes
- (name, node, revision, history, properties)
- VALUES
- " . implode(",\n", $inserts['nodes'])
- );
- }
- else
- {
- // _revs
- foreach ($inserts['revs'] AS $insert)
- {
- $this->controller->registry->db->query("
- REPLACE INTO c{$this->hash}_revs
- (revision, author, dateline, message, files)
- VALUES
- $insert"
- );
- }
-
- // _nodes
- foreach ($inserts['nodes'] AS $insert)
- {
- $this->controller->registry->db->query("
- REPLACE INTO c{$this->hash}_nodes
- (name, node, revision, history, properties)
- VALUES
- $insert"
- );
- }
- }
-
- $this->controller->registry->debug("TIME TO (RE)BUILD: " . $this->controller->registry->funct->fetch_microtime_diff($start));
- }
-}
-
-/*=====================================================================*\
-|| ###################################################################
-|| # $HeadURL$
-|| # $Id$
-|| ###################################################################
-\*=====================================================================*/
-?>
\ No newline at end of file