<?php

/**
 * A repository for files accessible via InstantCommons.
 */

class ICRepo extends LocalRepo {
	var $directory, $url, $hashLevels, $cache;
	var $fileFactory = array( 'ICFile', 'newFromTitle' );
	var $oldFileFactory = false;

	function __construct( $info ) {
		parent::__construct( $info );
		// Required settings
		$this->directory = $info['directory'];
		$this->url = $info['url'];
		$this->hashLevels = $info['hashLevels'];
		if(isset($info['cache'])){
			$this->cache = getcwd().'/images/'.$info['cache'];
		}
	}
}

/**
 * A file loaded from InstantCommons
 */
class ICFile extends LocalFile{
	static function newFromTitle($title,$repo){
		return new self($title, $repo);
	}

	/**
	 * Returns true if the file comes from the local file repository.
	 *
	 * @return bool
	 */
	function isLocal() {
		return true;
	}

	function load(){
		if (!$this->dataLoaded ) {
			if ( !$this->loadFromCache() ) {
				if(!$this->loadFromDB()){
					$this->loadFromIC();
				}
				$this->saveToCache();
			}
			$this->dataLoaded = true;
		}
	}

	/**
	 * Load file metadata from the DB
	 */
	function loadFromDB() {
		wfProfileIn( __METHOD__ );

		# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
		$this->dataLoaded = true;

		$dbr = $this->repo->getSlaveDB();

		$row = $dbr->selectRow( 'ic_image', $this->getCacheFields( 'img_' ),
			array( 'img_name' => $this->getName() ), __METHOD__ );
		if ( $row ) {
			if (trim($row->img_media_type)==NULL) {
				$this->upgradeRow();
				$this->upgraded = true;
			}
			$this->loadFromRow( $row );
			//This means that these files are local so the repository locations are local
			$this->setUrlPathLocal();
			$this->fileExists = true;
			//var_dump($this); exit;
		} else {
			$this->fileExists = false;
		}

		wfProfileOut( __METHOD__ );

		return $this->fileExists;
	}

		/**
	 * Fix assorted version-related problems with the image row by reloading it from the file
	 */
	function upgradeRow() {
		wfProfileIn( __METHOD__ );

		$this->loadFromIC();

		$dbw = $this->repo->getMasterDB();
		list( $major, $minor ) = self::splitMime( $this->mime );

		wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");

		$dbw->update( 'ic_image',
			array(
				'img_width' => $this->width,
				'img_height' => $this->height,
				'img_bits' => $this->bits,
				'img_media_type' => $this->type,
				'img_major_mime' => $major,
				'img_minor_mime' => $minor,
				'img_metadata' => $this->metadata,
			), array( 'img_name' => $this->getName() ),
			__METHOD__
		);
		$this->saveToCache();
		wfProfileOut( __METHOD__ );
	}

	function exists(){
		$this->load();
		return $this->fileExists;
	}

	/**
	 * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server
	 */
	function loadFromIC(){
		# Unconditionally set loaded=true, we don't want the accessors constantly rechecking
		$this->dataLoaded = true;
		$icUrl = $this->repo->directory.'&media='.$this->title->mDbkeyform;
		if($h = @fopen($icUrl, 'rb')){
			$contents = fread($h, 3000);
			$image = $this->api_xml_to_array($contents);
			if($image['fileExists']){
				foreach($image as $property=>$value){
					if($property=="url"){$value=$this->repo->url.$value; }
					$this->$property = $value;
				}
				if($this->curl_file_get_contents($this->repo->url.$image['url'], $this->repo->cache.'/'.$image['name'])){
					//Record the image
					$this->recordDownload("Downloaded with InstantCommons");

					//Then cache it
				}else{//set fileExists back to false
					$this->fileExists = false;
				}
			}
		}
	}

	function setUrlPathLocal(){
		global $wgScriptPath;
		$path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
		$this->repo->url = $path;//.'/'.rawurlencode($this->title->mDbkeyform);
		$this->repo->directory = $this->repo->cache;//.'/'.rawurlencode($this->title->mDbkeyform);

	}

	function getThumbPath( $suffix=false ){
		$path = $this->repo->cache;
		if ( $suffix !== false ) {
			$path .= '/thumb/' . rawurlencode( $suffix );
		}
		return $path;
	}
	function getThumbUrl( $suffix=false ){
		global $wgScriptPath;
	 	$path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
		if ( $suffix !== false ) {
			$path .= '/thumb/' . rawurlencode( $suffix );
		}
		return $path;
	}

	/**
	 * Convert the InstantCommons Server API XML Response to an associative array
	 */
	function api_xml_to_array($xml){
		preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match);
		preg_match_all("/(.*?=\".*?\")/",$match[1], $matches);
		foreach($matches[1] as $match){
			list($key,$value) = split("=",$match);
			$image[trim($key,'<" ')]=trim($value,' "');
		}
		return $image;
	}

	/**
	 * Use cURL to read the content of a URL into a string
	 * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
	 * @param string $url - the URL to fetch
	 * @param resource $fp - filename to write file contents to
	 * @param boolean $bg - call cURL in the background (don't hang page until complete)
	 * @param int $timeout - cURL connect timeout
	 */
	function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) {
		# Call curl in the background to download the file
		$cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &';
		wfDebug('Curl download initiated='.$cmd );
		$success = false;
		$file_contents = array();
		$file_contents['err'] = wfShellExec($cmd, $file_contents['return']);
		if($file_contents['err']==0){//Success
			$success = true;
		}
		return $success;
	}

	function getMasterDB() {
		if ( !isset( $this->dbConn ) ) {
			$class = 'Database' . ucfirst( $this->dbType );
			$this->dbConn = new $class( $this->dbServer, $this->dbUser,
				$this->dbPassword, $this->dbName, false, $this->dbFlags,
				$this->tablePrefix );
		}
		return $this->dbConn;
	}

	/**
	 * Record a file upload in the upload log and the image table
	 */
	private function recordDownload($comment='', $timestamp = false ){
		global $wgUser;

		$dbw = $this->repo->getMasterDB();

		if ( $timestamp === false ) {
			$timestamp = $dbw->timestamp();
		}
		list( $major, $minor ) = self::splitMime( $this->mime );

		# Test to see if the row exists using INSERT IGNORE
		# This avoids race conditions by locking the row until the commit, and also
		# doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
		$dbw->insert( 'ic_image',
			array(
				'img_name' => $this->getName(),
				'img_size'=> $this->size,
				'img_width' => intval( $this->width ),
				'img_height' => intval( $this->height ),
				'img_bits' => $this->bits,
				'img_media_type' => $this->type,
				'img_major_mime' => $major,
				'img_minor_mime' => $minor,
				'img_timestamp' => $timestamp,
				'img_description' => $comment,
				'img_user' => $wgUser->getID(),
				'img_user_text' => $wgUser->getName(),
				'img_metadata' => $this->metadata,
			),
			__METHOD__,
			'IGNORE'
		);

		if( $dbw->affectedRows() == 0 ) {
			# Collision, this is an update of a file
			# Update the current image row
			$dbw->update( 'ic_image',
				array( /* SET */
					'img_size' => $this->size,
					'img_width' => intval( $this->width ),
					'img_height' => intval( $this->height ),
					'img_bits' => $this->bits,
					'img_media_type' => $this->media_type,
					'img_major_mime' => $this->major_mime,
					'img_minor_mime' => $this->minor_mime,
					'img_timestamp' => $timestamp,
					'img_description' => $comment,
					'img_user' => $wgUser->getID(),
					'img_user_text' => $wgUser->getName(),
					'img_metadata' => $this->metadata,
				), array( /* WHERE */
					'img_name' => $this->getName()
				), __METHOD__
			);
		} else {
			# This is a new file
			# Update the image count
			$site_stats = $dbw->tableName( 'site_stats' );
			$dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
		}

		$descTitle = $this->getTitle();
		$article = new Article( $descTitle );

		# Add the log entry
		$log = new LogPage( 'icdownload' );
		$log->addEntry( 'InstantCommons download', $descTitle, $comment );

		if( $descTitle->exists() ) {
			# Create a null revision
			$nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false );
			$nullRevision->insertOn( $dbw );
			$article->updateRevisionOn( $dbw, $nullRevision );

			# Invalidate the cache for the description page
			$descTitle->invalidateCache();
			$descTitle->purgeSquid();
		}


		# Commit the transaction now, in case something goes wrong later
		# The most important thing is that files don't get lost, especially archives
		$dbw->immediateCommit();

		# Invalidate cache for all pages using this file
		$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
		$update->doUpdate();

		return true;
	}

}

