<?php  // vim:set et ts=4 sw=4 sts=4:
# Copyright (C) 2003-2014 hipplay Inc.
#
# This is UNPUBLISHED PROPRIETARY SOURCE CODE OF hipplay Inc.
# The copyright notice above does not evidence any actual or intended
# publication of such source code. The contents of this file may not
# be disclosed to third parties, copied or duplicated in any form, in
# whole or in part, without the prior written permission of
# hipplay Inc.
#
namespace HipPlayConnect\FileSystem;


use DB;
use DOMDocument;
use HipPlayConnect\AxDB;
use HipPlayConnect\AxFatalErrorException;
use HipPlayConnect\AxUnrecoverableErrorException;
use HipPlayConnect\FileSystem\Types\FileTypes;
use HipPlayConnect\REST\AxRESTController;

class AxRegularFile extends AxLocalFile
{	
	protected $metadata = null; // for audio and video
	
	public function __construct( $fsPath, $sambaPath, $URIBuilder )
	{
		parent::__construct( $fsPath, $sambaPath, $URIBuilder );

		$this->xmlFields =
			array_merge
			(
				$this->xmlFields,
				array
				(					
					'mimeType',
					'size',
					'mtime',				
					'tags', 
					'caption'	
				)
			);

        $FileTypes = FileTypes::getInstance();
		$this->data[ 'type'    ]  = $FileTypes->getClass( $fsPath );
		$this->data[ 'mimeType' ] = $FileTypes->getMIMEType( $fsPath );
	}
	
	// factory
	public static function getFile( $fsPath, $sambaPath, $URIBuilder = null ) {
        $fileTypes = FileTypes::getInstance();
        $className = '\\HipPlayConnect\\FileSystem\\' . $fileTypes->getFullClassName($fsPath);
		return new $className( $fsPath, $sambaPath, $URIBuilder );
	}
	

	public function getXML( $doc = null, $fields = null )
	{
		$returnText = is_null( $doc );
		if ( is_null( $doc ) )
			$doc = new DOMDocument( '1.0' );

		// get the parent (AxLocalFile) to create the file element before we add any subelements
		$root = parent::getXML($doc, $fields);			
				
		foreach ( is_null( $fields ) ? $this->xmlFields : $fields as $key ) {
						
			if($key=='tags') 
			{
			    if ( !is_null( $xmlTags = $this->getXMLTags($doc) ) )
				    $root->appendChild( $xmlTags );
			} 			
		}
			
		if ($returnText) {
			$doc->appendChild($root);
			return $doc->saveXML();
		} else {
			return $root;	
		}						
	}	
	
	
	public function getXMLTags( $doc = null )
	{
		$returnText = is_null( $doc );
		if ( is_null( $doc ) )
			$doc = new DOMDocument( '1.0' );
								
		$tags = $this->getTags();
		
		$tags_node = null;
		if(is_array($tags) && !empty($tags) )  {
			$tags_node = $doc->createElement('tags') ;

			foreach($tags as $tag) 
			{					
				$cur = $tags_node->appendChild($doc->createElement('tag'));
				$cur->appendChild($doc->createTextNode( $tag[0] ));
			}				
		}
	
		if ($returnText) {
			$doc->appendChild($tags_node);
			return $doc->saveXML();
		} else {
			return $tags_node;	
		}						
	}	

	
	//
	// Pre-caching:
	// About 2 to 2.5 seconds each ; way worse for flv (disabled)
	//
	// Post-caching:
	// About 250ms each once they are cached.
	//
	// no resolution for wmv (hidden in steams) 
	//
	
	// To be overridden by type-specific classes
	protected function getMetadata()
	{
	    return null;
	}

	public function getField( $key )
	{
		//
		// check if it's already loaded or if the parent knows how to get the field
		//
		if ( !is_null( $output = parent::getField( $key ) ) )
			return $output;
		
		switch ( $key )
		{	
			case 'caption':
				$this->data[ 'caption' ] = $this->getCaption();
				break;				
		}
		
		return
			array_key_exists( $key, $this->data )
			? $this->data[ $key ]
			: null;
	}

	public function addTags($tags)
	{

	    $filename = $this->fsPath;

		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$strsql = "";

		foreach($tags as $tag)
		{
			if($tag!='') {
				$strsql .= "REPLACE INTO `tags` (`fsPath`,`tag`) VALUES (".$dbConn->quoteSmart($filename).", " . $dbConn->quoteSmart($tag) . ");";
			}
		}

		if( $strsql != "" && DB::isError($dbConn->query($strsql)) )
			throw new AxUnrecoverableErrorException("Error inserting to metadata DB" .$dbConn->errorNative());
	}

	public function deleteTags()
	{
		$filename = $this->fsPath;
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$strsql = "DELETE FROM `tags` WHERE `fsPath`=".$dbConn->quoteSmart($filename);

		if(  DB::isError($dbConn->query($strsql)) )
			throw new AxUnrecoverableErrorException("ERROR - deleting from tags DB ".$dbConn->errorNative());
	}

	public function getTags()
	{
		$filename = $this->fsPath;
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$strsql = "SELECT tag FROM `tags` WHERE `fsPath`=".$dbConn->quoteSmart($filename);

		//$tags = $dbConn->getAll($strsql, DB_FETCHMODE_ORDERED);
		$tags = $dbConn->getAll($strsql, array(), DB_FETCHMODE_ORDERED);

		if(  DB::isError( $tags ) )
		    throw new AxUnrecoverableErrorException("ERROR - selecting from metadata DB ".$dbConn->errorNative());

		return $tags;
	}


	public function setCaption( $caption )
	{
		$filename = $this->fsPath;
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$strsql = "REPLACE INTO `metadata`  (`fsPath`,`caption`) VALUES (".$dbConn->quoteSmart($filename).", " . $dbConn->quoteSmart($caption) . ")";

		if( DB::isError($dbConn->query($strsql)) )
             throw new AxUnrecoverableErrorException("Error inserting to metadata DB" .$dbConn->errorNative());

        $this->data[ 'caption' ] = $caption;
	}

	public function getCaption()
	{
		$filename = $this->fsPath;
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$strsql = "SELECT caption FROM `metadata` WHERE `fsPath`=".$dbConn->quoteSmart($filename);

		$result = $dbConn->getOne($strsql);
		if( DB::isError($result) )
		     throw new AxUnrecoverableErrorException("ERROR -  inserting to metadata DB " .$dbConn->errorNative());

		return $result;
	}

    /**
     * Returns a hashtable path => caption
     *
     * @param mixed $what either the path of a directory or an array of file paths of assets
     *
     * @throws AxFatalErrorException
     * @throws AxUnrecoverableErrorException
     * @return array a hashtable path => caption
     */
	public static function getCaptionsFor( $what )
	{
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');

		$output = array();
		foreach( self::getFSPathConditions( $dbConn, $what ) as $condition )
		{
    		$strsql = "SELECT fsPath, caption FROM `metadata` WHERE " . $condition;
            /** @var object $res */
            $res = $dbConn->query( $strsql );
    		if( DB::isError($res) )
    		     throw new AxUnrecoverableErrorException("ERROR - loading captions " .$dbConn->errorNative());

            $row = NULL;
    		while ( $res->fetchInto( $row ) )
    			$output[ $row[ 'fsPath' ] ] = $row[ 'caption' ];
    	    $res->free();

		}

		return $output;
	}

    /**
     * Returns a hashtable path => array of tags
     *
     * @param mixed $what either the path of a directory or an array of file paths of assets
     *
     * @throws AxFatalErrorException
     * @throws AxUnrecoverableErrorException
     * @return array a hashtable path => array of tags
     */
	public static function getTagsFor( $what )
	{
		if(!($dbConn = AxDB::getInstance('hipserv')))
			throw new AxFatalErrorException('Cannot connect to hipserv db');
			
		$output = array();
		foreach( self::getFSPathConditions( $dbConn, $what ) as $condition )
		{
    		$strsql = "SELECT fsPath, tag FROM `tags` WHERE " . $condition;
            /** @var object $res */
            $res = $dbConn->query( $strsql );
    		if( DB::isError($res) )			
    		     throw new AxUnrecoverableErrorException("ERROR - loading tags " .$dbConn->errorNative());

            $row = NULL;
    		while ( $res->fetchInto( $row ) )
    		{
    		    if ( !isset( $row[ 'fsPath' ] ) )
    		        $row[ 'fsPath' ] = array();
    			$output[ $row[ 'fsPath' ] ][] = $row[ 'tag' ];
    		}
    		$res->free();
		}
		
		return $output;
	}

	//
	// actually sends the file to the browser, does not return a Resource (faster)
	// -- based on hombase/screens/download
	//
	public function download() {
		$mimeType = $this->getField( 'mimeType' );
		$size     = $this->getField( 'size' );
		$mtime    = $this->getField( 'mtime' );
		$name     = $this->getField( 'name' );
		$fsPath   = $this->fsPath;
	
		AxRegularFile::downloadFile( $fsPath, $size, $mtime, $name, $mimeType );
	}
	
	public static function downloadFileFromPath( $fsPath, $cache = false, $asAttachment = false, $delete = false ) {
		$stat  = oeutil_stat( $fsPath ); // PERFORMANCE: 12 ms
		$size  = $stat[ 7 ];
		$mtime = $stat[ 9 ];
		$name  = self::getBasename( $fsPath );
		
		$FileTypes = FileTypes::getInstance();
    	$mimeType = $FileTypes->getMIMEType( $fsPath );

		AxRegularFile::downloadFile( $fsPath, $size, $mtime, $name, $mimeType, $cache, $asAttachment, $delete );
	}
	
	public static function downloadFile( $fsPath, $size, $mtime, $name, $mimeType, $cache = false, $asAttachment = false, $delete = false )
	{
		//clean it out so that the screen does not appear twice
		//HACK: work around for ob_clean() not working in PHP < 4.2.
		//ob_clean();
		ob_end_clean();
		
		// serve ranges through apache directly (temporary link), should be garbage collected
		/*
		if ( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) {
		    $streamingLinksDir = '/var/axentra/streaming_links';
		    $linkPath = tempnam( $streamingLinksDir, 'link' );
		    unlink( $linkPath );
		    $linkPath .=
		        preg_match( '|\.([^/.]*)$|', $fsPath, $RE )
		        ? '.' . $RE[ 1 ]
		        : '';
		    symlink( $fsPath, $linkPath );
		    header( 'Location: /streaming_links/' . urlencode( substr( $linkPath, strlen( $streamingLinksDir . '/' ) ) ) );
		}
		*/
		
		$isUnder2GB = bccomp( $size, 2147483647 ) < 1;
		
		if ( $isUnder2GB )
            header( 'Accept-Ranges: bytes' );
            		
		if( $mimeType == 'application/octet-stream' || $asAttachment )
		{		
			//force download dialog
			header('Content-type: ' . $mimeType );
            header('Content-Disposition: attachment; filename="'. ( ereg( 'MSIE ([0-9]+\.[0-9]+[a-z]*);(.*)', $_SERVER[ 'HTTP_USER_AGENT' ] ) ? rawurlencode( $name ) : $name ) .'"' ); // SFN 20087022 bug 11969 
			/* These headers are ***REQUIRED*** for MS Internet Explorer to download the files. */
			header('Expires: 0');
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
			header('Pragma: public');
		}
		else
		{
			// Inline viewing (image, perhaps?).  Make it cacheable! (bug 8209)
			//$Etag = '"'.md5_file( $ThisFile->fsPath ).'"';
			//$Etag = '"'.$size .'"';
			if( AxRESTController::allowBrowserCaching( $mtime /*, $Etag*/ ) )
				exit;

			//display file inside browser
			header('Content-type: ' . $mimeType );
			header('Content-Disposition: inline; filename="'. ( ereg( 'MSIE ([0-9]+\.[0-9]+[a-z]*);(.*)', $_SERVER[ 'HTTP_USER_AGENT' ] ) ? rawurlencode( $name ) : $name ) .'"');
		}
		
        header( 'Expires: ' . ( $cache ? gmdate( 'D, d M Y H:i:s', time() + 3600 * 24 ).' GMT' : 0 ) );
		header( 'Content-transfer-encoding: binary' );


		// streaming for iPod mainly
	    if (
	        isset( $_SERVER[ 'HTTP_RANGE' ] ) &&
	        count( $range = explode( '-', substr( $_SERVER[ 'HTTP_RANGE' ], 6 ) ) ) == 2 &&
	        
	        // we cannot stream on files larger than 2GB
	        $isUnder2GB
	    )
	    {
	        $start = $range[ 0 ];
	        $end   = $range[ 1 ] == '' ? $size - 1 : $range[ 1 ];
	        header( 'Content-Range: bytes ' . $start . '-' . $end . '/' . $size );
            header( 'Content-Length: ' . ( $end - $start + 1 ) );
            header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 206 Partial' );

			header("X-Sendfile2: $fsPath $start-");

//		$MyFile = fopen( $fsPath, 'rb' );
//            fseek( $MyFile, $start );
//            $written = 0;
//            while( $MyFile && !feof($MyFile) && connection_status() == CONNECTION_NORMAL && $written < $end - $start + 1 )
//            {
//                $toWrite = min( 1024*1024, $end - $start + 1 - $written );
//                print( fread($MyFile, $toWrite) );
//                $written += $toWrite;
//            }
	    }
	    else {
			if ($delete) {
				header('Content-length: ' . $size);
				$MyFile = fopen( $fsPath, 'rb' );
				while ($MyFile && !feof($MyFile) && connection_status() == CONNECTION_NORMAL)
					print(fread($MyFile, 1024 * 1024));
				fclose ($MyFile);
			}
			else
				header("X-Sendfile: $fsPath");
	    }

		//breakPoint( 'end of download');

        if ($delete)
            unlink($fsPath);

		exit;
	}	

	public function getViewFSPath( $view )
	{
		return self::getViewFSPathForType( $view, $this->getField( 'type' ) );
	}	

    public static function getViewFSPathForType( $view, $type )
    {
        return '/var/www/html/images/' . $view . 's/' . $type . '.jpg';
    }
	
}
