<?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 DOMDocument;
use DOMElement;
use HipPlayConnect\AxRecoverableErrorException;
use HipPlayConnect\Configuration\AxBranding;
use HipPlayConnect\FileSystem\Types\FileTypes;

class AxFolder extends AxLocalFile
{
	protected $dirs = null;  // SF20090301 - use getDirs()  to access it. Should not be used outside loadDirsAndFiles(), getDirs()
	protected $files = null; // SF20090301 - use getFiles() to access it. Should not be used outside loadDirsAndFiles(), getFiles() ; actually, AxUser does not even use it!!!
	protected $fileFactory = 'AxRegularFile'; // set to AxAlbumElement for albums
	protected $subFolderClass = 'AxFolder';   // set to AxAlbum for album listings
	
	public function __construct( $fsPath, $sambaPath, $URIBuilder )
	{
		parent::__construct( $fsPath, $sambaPath, $URIBuilder );
		$this->data[ 'type' ] = 'folder';
		$this->xmlFields[] = 'nbFolders';
		$this->xmlFields[] = 'nbFiles';
		$this->xmlFields[] = 'sharingWithDMA';
		$this->xmlFields[] = 'deviceHref';
        $this->xmlFields[] = 'readOnly';
        $this->xmlFields[] = 'isCoreMount';		
		$this->xmlFields[] = 'mtime';		
	}
	
    public function &getDirs()
    {
        $this->loadDirsAndFiles( true );
        return $this->dirs;
    }
    
    public function &getFiles()
    {
        $this->loadDirsAndFiles( true );      
        return $this->files;
    }

    public function getFilteredFiles( $mediafilter )
    {
        $fileTypes = FileTypes::getInstance();
                
        $files = array();
        
        // media type filtering
        if ( $mediafilter == 'all' )
            foreach( $this->getFiles() as $file )
                $files[ $file ] = array( 'name' => $file );
        else
            foreach( $this->getFiles() as $file )
                if ( $fileTypes->getClass( $file ) == $mediafilter )
                    $files[ $file ] = array( 'name' => $file );
                    
        return $files;
    }
    
    /**
     * load dirs and files in a fast way, but does not load the list of dirs and files
     */
    private function loadNbDirsAndFiles()
    {
        if ( array_key_exists( 'nbFolders', $this->data ) )
            return;

// OLDER CODE            
/*
        if ( false === ( $counts = oeutil_getdirandfilecount( $this->fsPath ) ) )
           throw new AxRecoverableErrorException( null, 3 );
           
        $this->data[ 'nbFolders' ] = $counts[ 0 ];
        $this->data[ 'nbFiles'   ] = $counts[ 1 ];
*/

        // Bug 15927: consider folders with bad ownership/permissions as empty to avoid infinite "loading..." in the Flash UI
        $counts = oeutil_getdirandfilecount( $this->fsPath );
        list( $this->data[ 'nbFolders' ], $this->data[ 'nbFiles' ] ) = false === $counts ? array( 0, 0 ) : $counts;
    }    
    
	protected function loadDirsAndFiles( $sort = false )
	{
	    if ( !is_null( $this->dirs ) )
	        return;

        list( $this->dirs, $this->files ) = oeutil_getdirsandfiles( $this->fsPath );
        
        if ( !is_array( $this->dirs ) || !is_array( $this->files ) )
            throw new AxRecoverableErrorException( null, 3 ); 

        $this->data[ 'nbFolders' ] = count( $this->dirs ); 		    
        $this->data[ 'nbFiles' ] = count( $this->files );
        
        if ( $sort )
        {
            usort( $this->dirs,  'strcasecmp' );
            usort( $this->files, 'strcasecmp' );      
        }
	}

    //
    // returns the canonical XML of a directory (for a GET or as a result of an action, but without children)
    //
    public function getCanonicalXML() {
        $this->loadNbDirsAndFiles();
        return $this->getXML();     
    }

	protected function populateCaptionsTagsFSPath( &$files, $loadTags = true, $loadAllCaptionsAndTags = false )
	{
	    $FSPaths = array();
	    
        if ( $loadAllCaptionsAndTags )
        {
            foreach ( $files as $file => &$dataRef )
                $dataRef[ 'FSPath' ] = $this->fsPath . '/' . $file;
            unset( $dataRef );                    
            $captions = AxRegularFile::getCaptionsFor( $this->fsPath );
            if ( $loadTags )
                $tags     = AxRegularFile::getTagsFor( $this->fsPath );
        }
        else
        {
            foreach ( $files as $file => &$dataRef )
            {
                $dataRef[ 'FSPath' ] = $this->fsPath . '/' . $file;
                $FSPaths[]           = $this->fsPath . '/' . $file;
            }
            unset( $dataRef );
            $captions = AxRegularFile::getCaptionsFor( $FSPaths );
            if ( $loadTags )
                $tags     = AxRegularFile::getTagsFor( $FSPaths );
        }
      
        foreach ( $files as $name => &$dataRef ) {
            if ( isset( $captions[ $dataRef[ 'FSPath' ] ] ) )
                $dataRef[ 'caption' ] = $captions[ $dataRef[ 'FSPath' ] ];
            if ( $loadTags && isset( $tags[ $dataRef[ 'FSPath' ] ] ) )
                $dataRef[ 'tags' ] = $tags[ $dataRef[ 'FSPath' ] ];
        }
        unset( $dataRef );        
	}

    /**
     * Returns the Media RSS 2.0 feed for the folder
     *
     * @param         $selfURL
     * @param null    $baseURL
     * @param boolean $returnAsText (default: true)
     * @param bool    $appendMtime
     *
     * @return string|DOMDocument the feed as text if $returnAsText is true, as a DOMDocument otherwise
     */
	public function getMediaRSS20( $selfURL, $baseURL = null, $returnAsText = true, $appendMtime = false )
	{
        $fileTypes = FileTypes::getInstance();

        // Hack for PicLens for MacOS Intel only (does not handle cookies)
        $sessionQS = isset( $_GET[ session_name() ] ) ? '?' . urlencode( session_name() ) . '=' . urlencode( session_id() ) : '';
        $selfURL .= $sessionQS;
                
        // document
	    $mDoc = new DOMDocument( '1.0', 'UTF-8' );
	    
	    // RSS node
        $mRSSNode = $mDoc->createElement( 'rss' );
        $mRSSNode->setAttribute( 'version',     '2.0' );
        $mRSSNode->setAttribute( 'xmlns:media', 'http://search.yahoo.com/mrss/' );
        $mRSSNode->setAttribute( 'xmlns:atom',  'http://www.w3.org/2005/Atom'   );
        
	    // channel node and channel info
	    $mChannelNode = $mDoc->createElement( 'channel' );
	    $mChannelNode->appendChild( $mDoc->createElement( 'title', htmlspecialchars( $this->getField( 'name' ) ) ) );
	    
	    $mChannelNode->appendChild( $mDoc->createElement( 'link', htmlspecialchars( $selfURL ) ) );
	    
        $mChannelNode->appendChild( $mDoc->createElement( 'description' ) );
        $mChannelNode->appendChild( $mDoc->createElement( 'atom:icon', htmlspecialchars( ( is_null( $baseURL ) ? self::getProtocolAndHost() : $baseURL ) . AxBranding::get('bannerIcon') ) . $sessionQS ) );
        
        $mAtomLinkNode = $mDoc->createElement( 'atom:link' );
        $mAtomLinkNode->setAttribute( 'href', $selfURL );
        $mAtomLinkNode->setAttribute( 'rel', 'self' );
        $mAtomLinkNode->setAttribute( 'type', 'application/rss+xml' );
        $mChannelNode->appendChild( $mAtomLinkNode );

        $files = $this->getFilteredFiles( 'image' );
                      
        $this->populateCaptionsTagsFSPath( $files, false );
        
        foreach ( $files as $name => $data )
		{
		    $guid = call_user_func( array( $this->URIBuilder, 'getURIForFSPath' ), $this->fsPath . '/' . $name );
            $href = ( is_null( $baseURL ) ? self::getProtocolAndHost() : $baseURL ) . $guid;
		        
		    $mItemNode = $mDoc->createElement( 'item' );
            $mItemNode->appendChild( $mDoc->createElement( 'title', htmlspecialchars( $name ) ) );
            $mItemNode->appendChild(
                $mDoc->createElement(
                    'description',
                    htmlspecialchars(
                        '<a href="' . htmlspecialchars( $href ) . '/webview' . $sessionQS . '" title="' . htmlspecialchars( $name ) . '"><img border="0" src="' . htmlspecialchars( $href . '/thumbnail' . $sessionQS ) . '" alt="' . $name . '" /></a>' .
                        ( empty( $data[ 'caption' ] ) ? '' : '<br />' . htmlspecialchars( $data[ 'caption' ] ) )
                    )
                )
            );
            
            if ( $appendMtime )
            {
                $lstat = oeutil_stat( $this->fsPath . '/' . $name );
                $mItemNode->appendChild( $mDoc->createElement( 'pubDate', htmlspecialchars( date( 'r', $lstat[ 'mtime' ] ) ) ) );
            }
                
            $mItemNode->appendChild( $mDoc->createElement( 'link', htmlspecialchars( $href . '/webview' . $sessionQS ) ) );

            $mGUIDNode = $mDoc->createElement( 'guid', htmlspecialchars( $guid ) );
            $mGUIDNode->setAttribute( 'isPermaLink', 'false' );
            $mItemNode->appendChild( $mGUIDNode );

            $mMediaThumbnailNode =   $mDoc->createElement( 'media:thumbnail' );
            $mMediaThumbnailNode->setAttribute( 'url',  $href . '/thumbnail' . $sessionQS );
            $mItemNode->appendChild( $mMediaThumbnailNode );
            
            $mMediaContentNode   =   $mDoc->createElement( 'media:content'   );           
            $mMediaContentNode->setAttribute( 'url',  $href . '/webview' . ( $sessionQS ? $sessionQS . '&' : '?' ) . 'filename=' . urlencode( $name ) ); // added filename for PicLens Lite to show the full URLs
            $mMediaContentNode->setAttribute( 'type', $fileTypes->getMIMEType( $name ) );
            $mItemNode->appendChild( $mMediaContentNode );

            $mChannelNode->appendChild( $mItemNode );
		}

        $mRSSNode->appendChild( $mChannelNode );
        $mDoc->appendChild( $mRSSNode );
        
	    return
	        $returnAsText
	        ? $mDoc->saveXML()
	        : $mDoc;   
	}
	
	
	public function getLightAssetsXML() {
        $mDoc = new DOMDocument( '1.0' );
        $mRoot = $mDoc->appendChild( $this->getXMLForLightAssets( $mDoc ) );

        $FileTypes = FileTypes::getInstance();
        foreach ( $this->getFiles() as $file )
        {
            $mFileNode = $mDoc->createElement( 'file' );
            $mFileNode->setAttribute( 'name', $file );
            $mFileNode->setAttribute( 'type', $FileTypes->getClass( $file ) );
            $mFileNode->setAttribute( 'href', call_user_func( array( $this->URIBuilder, 'getURIForFSPath' ), $this->fsPath . '/' . $file ) );
            $mRoot->appendChild( $mFileNode );
        }

        return $mDoc->saveXML();
	}
	
	
	public function getMtimeXML() {
	    return $this->getXML( null, array( 'href', 'mtime' ) );
	} 
	
	
	//
	// returns an XML node to use in the directory listing (as a subdirectory)
	//
	public function getXMLForDirectoryListing( $doc, $isMobile = false ) {
	    if ( !$isMobile )
	        $this->loadNbDirsAndFiles();
	    return $this->getXML( $doc, $isMobile ? array( 'name', 'type', 'href' ) : array( 'name', 'type', 'href', 'sharingWithDMA', 'deviceHref', 'class', 'readOnly', 'isCoreMount', 'nbFolders', 'nbFiles', 'mtime') );
	}
	
    protected function getXMLForLightAssets( $mDoc ) {
        return $this->getXML( $mDoc, array( 'href' ) );
    }	
	
    /**
     * Returns an XML representation of the directory hierarchy under the current folder, as string
     *
     * @param string $descendantFsPath if not null, only return $descendantFsPath, its ancestors and their children
     * @return string the directory hierarchy XML
     */
	public function getXMLDirectoryTree( $descendantFsPath = null ) {
	
		// only supports AxFolder
		if ( $this->subFolderClass != 'AxFolder' )
			return null;
			
        $doc = new DOMDocument( '1.0' );
		$root = $this->getXML( $doc, array( 'name', 'type', 'href', 'sharingWithDMA', 'deviceHref', 'class', 'readOnly', 'isCoreMount', 'nbFolders', 'nbFiles', 'mtime' ) );
        $root->setAttribute( 'mediafilter', 'all' );
        $root->setAttribute( 'folderfilter', 'true' );
        $root->setAttribute( 'nbFilteredAssets', $this->getField( 'nbFolders' ) + $this->getField( 'nbFiles' ) );		
		foreach ( $this->getDirs() as $dir )
		    $root->appendChild( self::getFolderXMLDirectoryTree( $doc, $this->fsPath, $dir, $descendantFsPath, $this->URIBuilder ) );

		$doc->appendChild($root);
		return $doc->saveXML();
	}

	/**
	 * Returns an XML representation of the current folder, its ancestors and their children, as a string
     *
     * @return string the directory hierarchy XML 
	 */
    public function getXMLAncestorTree() {
    
        // only supports AxFolder
        if ( $this->subFolderClass != 'AxFolder' )
            return null;
            
        // find the media source root
        $parentFsPath = '';
        $dir = '';
        foreach( explode( '/', substr( $this->fsPath, 1 ) ) as $dir )
        {
            if ( '' != call_user_func( array( $this->URIBuilder, 'getURIForFSPath' ), $parentFsPath . '/' . $dir ) )
                break;
            $parentFsPath .= '/' . $dir;
        }
        
        $doc = new DOMDocument( '1.0' );
        $doc->appendChild( self::getFolderXMLDirectoryTree( $doc, $parentFsPath, $dir, $this->fsPath, $this->URIBuilder ) );
        return $doc->saveXML();
    }	
	
	//
	// faster, non OO directory tree function
	// Warning: do not override static methods, it does not work
	//
	private static function getFolderXMLDirectoryTree( DOMDocument $doc, $parentFsPath, $dir, $descendantFsPath, $URIBuilder ) {
        list( $dirs, $files ) = oeutil_getdirsandfiles( $parentFsPath . '/' . $dir );        
        if ( !is_array( $dirs ) || !is_array( $files ) )
            throw new AxRecoverableErrorException( null, 3 ); 
        usort( $dirs, 'strcasecmp' );

        /** @var DOMElement $root */
		$root = $doc->createElement( 'file' ) ;
		$root->setAttribute( 'name', $dir );
		$root->setAttribute( 'type', 'folder' );
		if ( !is_null( $URIBuilder) )
			$root->setAttribute( 'href', call_user_func( array( $URIBuilder, 'getURIForFSPath' ), $parentFsPath . '/' . $dir ) );
			
//	    require_once( APPLICATIONS_DIR . 'filemanager/MediaSharing.php' );
//	    if ( $swdma = MediaSharing::getSharingStatus( $parentFsPath . '/' . $dir ) ) // = on purpose, do not replace it with an ==
//		    $root->setAttribute( 'sharingWithDMA', $swdma );
		if ( !is_null( $deviceHref = self::_getDeviceHref( $parentFsPath . '/' . $dir ) ) ) // SF20090311 - calling statically a non-static method is not great but it works
		    $root->setAttribute( 'deviceHref', $deviceHref );
		if ( !is_null( $class = self::getClass( $parentFsPath . '/' . $dir ) ) )
		    $root->setAttribute( 'class', $class );
		// shortcut: no readOnly attribute here, so far, only ExternalStorage is readOnly
		// shortcut: no isCoreMount attribute here either, since only Media Sources can be main storage
		    
		$root->setAttribute( 'nbFolders', $d = count( $dirs ) );
		$root->setAttribute( 'nbFiles', $f = count( $files ) );
        $root->setAttribute( 'mediafilter', 'all' );
        $root->setAttribute( 'folderfilter', 'true' );
        $root->setAttribute( 'nbFilteredAssets', $d + $f );
        $root->setAttribute( 'mtime', filemtime( $parentFsPath . '/' . $dir ) ); // about 3.4 ms / dir 		

        // if a descendant is specified, recurse only if we're an ancestor of the descendant
        if ( is_null( $descendantFsPath ) || substr( $descendantFsPath . '/', 0, strlen( $parentFsPath . '/' . $dir . '/' ) ) == $parentFsPath . '/' . $dir . '/' )
            foreach ( $dirs as $subdir )
        		$root->appendChild( self::getFolderXMLDirectoryTree( $doc, $parentFsPath . '/' . $dir, $subdir, $descendantFsPath, $URIBuilder ) );

		return $root;	
	}
	
	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 'nbFolders':
			    //fall through...
			case 'nbFiles':
				$this->loadDirsAndFiles();
				break;
				
			case 'sharingWithDMA':
			    return $this->getSharingWithDMA();
			    break;
			    
			case 'deviceHref':
			    return $this->_getDeviceHref();
			    break;

			// we cannot delete, copy, rename, move /home/0external, nor can we upload or create folders in it 
			case 'readOnly':
			    return $this->fsPath == self::EXTERNAL_FILESYSTEM ? 'true' : null;
			    
			case 'isCoreMount':
			    return $this->_isCoreMount();
		}
		
		return
			array_key_exists( $key, $this->data )
			? $this->data[ $key ]
			: null;
	}
	
	protected function getSharingWithDMA() {
//	    require_once( APPLICATIONS_DIR . 'filemanager/MediaSharing.php' );
//	    return MediaSharing::getSharingStatus( $this->fsPath );
        return 'false';
	}

	public function getViewFSPath( $view )
	{
		return '/var/www/html/html/images/' . $view . 's/folder.gif';
	}

    /**
     * @deprecated
     * @internal param null $fsPath
     * @return NULL
     */
	private function _getDeviceHref( /* $fsPath = null */ )
	{
	    // disabled this for MediaCaster
	    return null;
	    
//	    // Shortcut: non-users do not need deviceHrefs (anyway the only folders they can get are the albums)
//	    if ( !AxUser::isLoggedIn() )
//	       return null;
//
//	    $deviceHref = null;
//
//        // default folder: the current one
//	    if ( is_null( $fsPath ) )
//	        $fsPath = $this->fsPath;
//
//	    // Plugs on which FamLib, MyLib and MyComp are on eSATA
//	    $features = getFeatures();
//	    if
//	    (
//            !empty( $features[ 'isOnlyUSBStorage' ] ) &&
//            !empty( $features[ 'eSataNotRAID'     ] )
//        )
//        {
//            global $Branding;
//            require_once( MODULE . 'classes/AxMountsHelper.php' );
//            if
//            (
//                $fsPath == AxUser::FAMILY_FILESYSTEM ||
//                $fsPath == AxUser::getRootPath() . $Branding[ 'dirMyLibrary' ] ||
//                $fsPath == AxUser::getRootPath() . $Branding[ 'dirMyComputers' ]
//            )
//            {
//                // if SATA0 is internal, it is not ejectable
//                if (
//                    !empty( $features[ 'internalDrives' ] ) &&
//                    strpos( $features[ 'internalDrives' ], 'SATA0' ) !== false
//                )
//                    return null;
//
//                if ( $deviceId = AxMountsHelper::getESATADeviceId() ) // used equal on purpose
//                    return AxRESTController::URI_PREFIX . 'usb/devices/' . $deviceId;
//            }
//        }
//
//        $USBDevicesParentFolder =
//           empty( $features[ 'isOnlyUSBStorage' ] )
//           // USB devices on non-plugs are mounted under /home/0common
//           ? $USBDevicesParentFolder = AxUser::FAMILY_FILESYSTEM
//           // USB devices on plugs are mounted under /home/0external
//           : AxUser::EXTERNAL_FILESYSTEM;
//
//	    // if the file is a child of /home/0external, return its deviceHref if possible
//	    if
//	    (
//	        substr( $fsPath, 0, $l = strlen( $USBDevicesParentFolder ) + 1 ) == $USBDevicesParentFolder . '/' && // starts with /home/0external/ or /home/0common/
//	        strpos( $fsPath, '/', $l ) === FALSE // no slash after /home/0external/
//	    )
//	    {
//	        // Bug 16788: [Roxio] Show folders in ExternalStorage as devices
//	        if ( !empty( $features[ 'isVM' ] ) )
//	           return 'dummy';
//
//            if ( false !== ( $fd = @fopen( '/proc/mounts', 'r' ) ) )
//            {
//                // match /dev/sdc1 /home/0external/usb1 ...
//                $regexp =
//                    '|^/dev/([^ ]+?)[0-9]+ ' .
//
//                    // escape fsPath
//                    preg_quote(
//
//                        // /proc/mounts escapes some chars
//                        strtr(
//                            $fsPath,
//                            array(
//                                "\t" => '\\011',
////                                "\n" => '\\012',
////                                "\r" => '\\015',
//                                ' '  => '\\040',
//                                '\\' => '\\134'
//                            )
//                        ),
//
//                        '|'
//                    ) .
//
//                    ' |';
//
//                while ( !feof( $fd ) )
//                    if ( preg_match( $regexp, fgets( $fd ), $matches ) )
//                    {
//                        $deviceHref = AxRESTController::URI_PREFIX . 'usb/devices/' . $matches[ 1 ];
//                        break;
//                    }
//                fclose( $fd );
//            }
//	    }
//
//	    return $deviceHref;
	}

    /**
     * @deprecated
     * @return NULL
     */
    private function _isCoreMount()
    {
        // disabled for MediaCaster
        return null;
        
//        // Shortcut: non-users do not need deviceHrefs (anyway the only folders they can get are the albums)
//        if ( !AxUser::isLoggedIn() )
//           return null;
//
//        // Plugs on which FamLib, MyLib and MyComp are on eSATA
//        $features = getFeatures();
//        if
//        (
//            !empty( $features[ 'isOnlyUSBStorage' ] ) &&
//            !empty( $features[ 'eSataNotRAID'     ] )
//        )
//        {
//            global $Branding;
//            if
//            (
//                (
//                    $this->fsPath == AxUser::FAMILY_FILESYSTEM ||
//                    $this->fsPath == AxUser::getRootPath() . $Branding[ 'dirMyLibrary' ] ||
//                    $this->fsPath == AxUser::getRootPath() . $Branding[ 'dirMyComputers' ]
//                )
//            )
//                return 'true';
//        }
//
//        return null;
    }

//    public static function handleMoveResource( AxFile $resource, $fsPath )
//    {
//        // Make sure we're using absolute paths
//        $fsPath = realpath( $fsPath );
//
//        // maintain sharing for folders
//        if ( $resource->getField( 'type' ) == 'folder' )
//        {
////            require_once( APPLICATIONS_DIR . 'filemanager/MediaSharing.php' );
//
//            $oldFSPath = $resource->getFSPath();
//
//            // need to call getSharingStatus() to force a shares loading before we move the folder
//            // otherwise the shares under the moved folder won't be loaded because its path would have changed
//            // moveShares relies on what was loaded
////            MediaSharing::getSharingStatus( $oldFSPath );
//
//            $resource->moveTo( $fsPath );
//
//            $needToUpdateShares = MediaSharing::moveShares( $oldFSPath, $resource->getFSPath() );
//        }
//        else
//        {
//            $resource->moveTo( $fsPath );
//            $needToUpdateShares = false;
//        }
//
//        return $needToUpdateShares;
//    }


    private static function getProtocolAndHost()
    {
        // $_SERVER['HTTP_HOST'] includes the port number if specified --
        // strip it off
        $host = preg_replace( '/:.*$/', '', $_SERVER['HTTP_HOST'] );

        return USE_SSL
                ? 'https://' . $host . HTTPS_SUFFIX
                : 'http://'  . $host . HTTP_SUFFIX;
    }

}

        
