<?php
/**
 * Akeeba Engine
 * The modular PHP5 site backup engine
 * @copyright Copyright (c)2009 Nicholas K. Dionysopoulos
 * @license GNU GPL version 3 or, at your option, any later version
 * @package akeebaengine
 * @version $Id: jpa.php 78 2010-03-04 17:59:01Z nikosdion $
 */

// Protection against direct access
defined('AKEEBAENGINE') or die('Restricted access');

/**
 * JoomlaPack Archive creation class
 *
 * JPA Format 1.0 implemented, minus BZip2 compression support
 */
class AEArchiverJpa extends AEAbstractArchiver
{
	/** @var integer How many files are contained in the archive */
	private $_fileCount = 0;

	/** @var integer The total size of files contained in the archive as they are stored */
	private $_compressedSize = 0;

	/** @var integer The total size of files contained in the archive when they are extracted to disk. */
	private $_uncompressedSize = 0;

	/** @var string The name of the file holding the ZIP's data, which becomes the final archive */
	private $_dataFileName;

	/** @var string Standard Header signature */
	private $_archive_signature = "\x4A\x50\x41";

	/** @var string Entity Block signature */
	private $_fileHeader = "\x4A\x50\x46";

	/** @var string Marks the split archive's extra header */
	private $_extraHeaderSplit = "\x4A\x50\x01\x01"; //

	/** @var bool Should I use Split ZIP? */
	private $_useSplitZIP = false;

	/** @var int Maximum fragment size, in bytes */
	private $_fragmentSize = 0;

	/** @var int Current fragment number */
	private $_currentFragment = 1;

	/** @var int Total number of fragments */
	private $_totalFragments = 1;

	/** @var string Archive full path without extension */
	private $_dataFileNameBase = '';

	/** @var bool Should I store symlinks as such (no dereferencing?) */
	private $_symlink_store_target = false;

	/**
	 * Extend the bootstrap code to add some define's used by the JPA format engine
	 * @see backend/akeeba/abstract/AEAbstractArchiver#__bootstrap_code()
	 */
	protected function __bootstrap_code()
	{
		if(!defined('_AKEEBA_COMPRESSION_THRESHOLD'))
		{
			$config =& AEFactory::getConfiguration();
			define("_AKEEBA_COMPRESSION_THRESHOLD", $config->get('engine.archiver.common.big_file_threshold')); // Don't compress files over this size

			define( '_JPA_MAJOR', 1 ); // JPA Format major version number
			define( '_JPA_MINOR', 1 ); // JPA Format minor version number
		}
		parent::__bootstrap_code();
	}

	/**
	 * Initialises the archiver class, creating the archive from an existent
	 * installer's JPA archive.
	 *
	 * @param string $sourceJPAPath Absolute path to an installer's JPA archive
	 * @param string $targetArchivePath Absolute path to the generated archive
	 * @param array $options A named key array of options (optional)
	 * @access public
	 */
	public function initialize( $targetArchivePath, $options = array() )
	{
		AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AkeebaPackerJPA :: new instance - archive $targetArchivePath");
		$this->_dataFileName = $targetArchivePath;

		// NEW 2.3: Should we enable Split ZIP feature?
		$registry =& AEFactory::getConfiguration();
		$fragmentsize = $registry->get('engine.archiver.common.part_size', 0);
		if($fragmentsize >= 65536)
		{
			// If the fragment size is AT LEAST 64Kb, enable Split ZIP
			$this->_useSplitZIP = true;
			$this->_fragmentSize = $fragmentsize;

			// Indicate that we have at least 1 part
			$statistics =& AEFactory::getStatistics();
			$statistics->updateMultipart(1);
			$this->_totalFragments = 1;

			AEUtilLogger::WriteLog(_AE_LOG_INFO, "AkeebaPackerJPA :: Spanned JPA creation enabled");
			$this->_dataFileNameBase = dirname($targetArchivePath).DS.basename($targetArchivePath,'.jpa');
			$this->_dataFileName = $this->_dataFileNameBase.'.j01';
		}

		// NEW 2.3: Should I use Symlink Target Storage?
		$dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks', true);
		if(!$dereferencesymlinks)
		{
			// We are told not to dereference symlinks. Are we on Windows?
			if (function_exists('php_uname'))
			{
				$isWindows = stristr(php_uname(), 'windows');
			}
			else
			{
				$isWindows = (DS == '\\');
			}
			// If we are not on Windows, enable symlink target storage
			$this->_symlink_store_target = !$isWindows;
		}

		// Try to kill the archive if it exists
		AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "AkeebaPackerJPA :: Killing old archive");
		$fp = @fopen( $this->_dataFileName, "wb" );
		if (!($fp === false)) {
			@ftruncate( $fp,0 );
			@fclose( $fp );
		} else {
			if( file_exists($this->_dataFileName) ) @unlink( $this->_dataFileName );
			@touch( $this->_dataFileName );
		}

		// Write the initial instance of the archive header
		$this->_writeArchiveHeader();
		if($this->getError()) return;
	}

	/**
	 * Updates the Standard Header with current information
	 */
	public function finalize()
	{
		// If Spanned JPA and there is no .jpa file, rename the last fragment to .jpa
		if($this->_useSplitZIP)
		{
			$extension = substr($this->_dataFileName, -3);
			if($extension != '.jpa')
			{
				AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Renaming last JPA part to .JPA extension');
				$newName = $this->_dataFileNameBase.'.jpa';
				if(!@rename($this->_dataFileName, $newName))
				{
					$this->setError('Could not rename last JPA part to .JPA extension.');
					return false;
				}
				$this->_dataFileName = $newName;
			}

			// Finally, point to the first part so that we can re-write the correct header information
			if($this->_totalFragments > 1)
			{
				$this->_dataFileName = $this->_dataFileNameBase.'.j01';
			}
		}

		// Re-write the archive header
		$this->_writeArchiveHeader();

		if($this->getError()) return;
	}

	/**
	 * Returns a string with the extension (including the dot) of the files produced
	 * by this class.
	 * @return string
	 */
	public function getExtension()
	{
		return '.jpa';
	}


	/**
	 * The most basic file transaction: add a single entry (file or directory) to
	 * the archive.
	 *
	 * @param bool $isVirtual If true, the next parameter contains file data instead of a file name
	 * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is true
	 * @param string $targetName The (relative) file name under which to store the file in the archive
	 * @return True on success, false otherwise
	 * @since 1.2.1
	 * @access protected
	 * @abstract
	 */
	protected function _addFile( $isVirtual, &$sourceNameOrData, $targetName )
	{
		static $configuration;
		static $timer;

		if($isVirtual)
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (virtual data)");
		else AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Adding $targetName to archive (source: $sourceNameOrData)");

		if(!$configuration)
		{
			$configuration =& AEFactory::getConfiguration();
		}
		if(!$timer)
		{
			$timer =& AEFactory::getTimer();
		}

		// Initialize archive file pointer
		$fp = null;

		if(!$this->processingFile)
		{
			// See if it's a directory
			$isDir = $isVirtual ? false : is_dir($sourceNameOrData);
			// See if it's a symlink (w/out dereference)
			$isSymlink = false;
			if($this->_symlink_store_target)
			{
				$isSymlink = is_link($sourceNameOrData);
			}

			// Get real size before compression
			if($isVirtual)
			{
				$fileSize = strlen($sourceNameOrData);
			}
			else
			{
				if($isSymlink)
				{
					$fileSize = strlen( @readlink($sourceNameOrData) );
				}
				else
				{
					$fileSize = $isDir ? 0 : @filesize($sourceNameOrData);
				}
			}

			// Decide if we will compress
			if ($isDir || $isSymlink) {
				$compressionMethod = 0; // don't compress directories...
			} else {
				// Do we have plenty of memory left?
				$memLimit = ini_get("memory_limit");
				if( is_numeric($memLimit) && ($memLimit < 0) ) $memLimit = ""; // 1.2a3 -- Rare case with memory_limit < 0, e.g. -1Mb!
				if (($memLimit == "") || ($fileSize >= _AKEEBA_COMPRESSION_THRESHOLD)) {
					// No memory limit, or over 1Mb files => always compress up to 1Mb files (otherwise it times out)
					$compressionMethod = ($fileSize <= _AKEEBA_COMPRESSION_THRESHOLD) ? 1 : 0;
				} elseif ( function_exists("memory_get_usage") ) {
					// PHP can report memory usage, see if there's enough available memory; Joomla! alone eats about 5-6Mb! This code is called on files <= 1Mb
					$memLimit = $this->_return_bytes( $memLimit );
					$availableRAM = $memLimit - memory_get_usage();
					$compressionMethod = (($availableRAM / 2.5) >= $fileSize) ? 1 : 0;
				} else {
					// PHP can't report memory usage, compress only files up to 512Kb (conservative approach) and hope it doesn't break
					$compressionMethod = ($fileSize <= 524288) ? 1 : 0;
				}
			}

			$compressionMethod = function_exists("gzcompress") ? $compressionMethod : 0;

			$storedName = $targetName;

			/* "Entity Description BLock" segment. */
			$unc_len = &$fileSize; // File size
			$storedName .= ($isDir) ? "/" : "";

			if ($compressionMethod == 1) {
				if($isVirtual)
				{
					$udata =& $sourceNameOrData;
				}
				else
				{
					// Get uncompressed data
					$udata = @file_get_contents( $sourceNameOrData ); // PHP > 4.3.0 saves us the trouble
				}

				if ($udata === FALSE) {
					// Unreadable file, skip it.
					$this->setWarning( sprintf('Unreadable file %s. Check permissions', $sourceNameOrData));
					return false;
				} else {
					// Proceed with compression
					$zdata   = @gzcompress($udata);
					if ($zdata === false) {
						// If compression fails, let it behave like no compression was available
						$c_len = &$unc_len;
						$compressionMethod = 0;
					} else {
						unset( $udata );
						$zdata   = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
						$c_len   = strlen($zdata);
					}
				}
			} else {
				$c_len = $unc_len;
			}

			$this->_compressedSize += $c_len; // Update global data
			$this->_uncompressedSize += $fileSize; // Update global data
			$this->_fileCount++;

			// Get file permissions
			$perms = $isVirtual ? 0777 : @fileperms( $sourceNameOrData );

			// Calculate Entity Description Block length
			$blockLength = 21 + strlen($storedName) ;

			// Get file type
			if( (!$isDir) && (!$isSymlink) ) { $fileType = 1; }
				elseif($isSymlink) { $fileType = 2;	}
				elseif($isDir) { $fileType = 0;	}

			// If it's a split ZIP file, we've got to make sure that the header can fit in the part
			if($this->_useSplitZIP)
			{
				// Compare to free part space
				clearstatcache();
				$current_part_size = @filesize($this->_dataFileName);
				$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
				if($free_space <= $blockLength)
				{
					// Not enough space on current part, create new part
					if(!$this->_createNewPart())
					{
						$this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
						return false;
					}
				}
			}

			// Open data file for output
			$fp = @fopen( $this->_dataFileName, "ab");
			if ($fp === false)
			{
				$this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
				return;
			}
			$this->_fwrite( $fp, $this->_fileHeader ); // Entity Description Block header
			if($this->getError()) return;
			$this->_fwrite( $fp, pack('v', $blockLength) ); // Entity Description Block header length
			$this->_fwrite( $fp, pack('v', strlen($storedName) ) ); // Length of entity path
			$this->_fwrite( $fp, $storedName ); // Entity path
			$this->_fwrite( $fp, pack('C', $fileType ) ); // Entity type
			$this->_fwrite( $fp, pack('C', $compressionMethod ) ); // Compression method
			$this->_fwrite( $fp, pack('V', $c_len ) ); // Compressed size
			$this->_fwrite( $fp, pack('V', $unc_len ) ); // Uncompressed size
			$this->_fwrite( $fp, pack('V', $perms ) ); // Entity permissions

			// Cache useful information about the file
			$configuration->set('volatile.engine.archiver.sourceNameOrData', $sourceNameOrData);
			$configuration->set('volatile.engine.archiver.unc_len', $unc_len);
		}
		else
		{
			// Create a file pointer to the archive file
			$fp = @fopen( $this->_dataFileName, "ab");
			if ($fp === false)
			{
				$this->setError("Could not open archive file '{$this->_dataFileName}' for append!");
				return;
			}
		}

		/* "File data" segment. */
		if ($compressionMethod == 1) {
			if(!$this->_useSplitZIP)
			{
				// Just dump the compressed data
				$this->_fwrite( $fp, $zdata );
				if($this->getError()) return;
			}
			else
			{
				// Split ZIP. Check if we need to split the part in the middle of the data.
				clearstatcache();
				$current_part_size = @filesize($this->_dataFileName);
				$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
				if($free_space >= strlen($zdata) )
				{
					// Write in one part
					$this->_fwrite( $fp, $zdata );
					if($this->getError()) return;
				}
				else
				{
					$bytes_left = strlen($zdata);

					while($bytes_left > 0)
					{
						clearstatcache();
						$current_part_size = @filesize($this->_dataFileName);
						$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);

						// Split between parts - Write first part
						$this->_fwrite( $fp, $zdata, min(strlen($zdata), $free_space) );
						if($this->getError()) return;

						// Get the rest of the data
						$bytes_left = strlen($zdata) - $free_space;

						if($bytes_left > 0)
						{
							// Create new part
							if(!$this->_createNewPart())
							{
								// Die if we couldn't create the new part
								$this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
								return false;
							}
							else
							{
								// Close the old data file
								fclose($fp);
								// Open data file for output
								$fp = @fopen( $this->_dataFileName, "ab");
								if ($fp === false)
								{
									$this->setError("Could not open archive file {$this->_dataFileName} for append!");
									return false;
								}
							}
							$zdata = substr($zdata, -$bytes_left);
						}
					}
				}
			}
			unset( $zdata );
		} elseif ( (!$isDir) && (!$isSymlink) ) {
			if($isVirtual)
			{
				if(!$this->_useSplitZIP)
				{
					// Just dump the data
					$this->_fwrite( $fp, $sourceNameOrData );
					if($this->getError()) return;
				}
				else
				{
					// Split ZIP. Check if we need to split the part in the middle of the data.
					clearstatcache();
					$current_part_size = @filesize($this->_dataFileName);
					$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
					if($free_space >= strlen($sourceNameOrData) )
					{
						// Write in one part
						$this->_fwrite( $fp, $sourceNameOrData );
						if($this->getError()) return;
					}
					else
					{
						$bytes_left = strlen($sourceNameOrData);

						while($bytes_left > 0)
						{
							clearstatcache();
							$current_part_size = @filesize($this->_dataFileName);
							$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);

							// Split between parts - Write first part
							$this->_fwrite( $fp, $sourceNameOrData, min(strlen($sourceNameOrData), $free_space) );
							if($this->getError()) return;

							// Get the rest of the data
							$rest_size = strlen($sourceNameOrData) - $free_space;
							if($rest_size > 0)
							{
								// Create new part
								if(!$this->_createNewPart())
								{
									// Die if we couldn't create the new part
									$this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
									return false;
								}
								else
								{
									// Close the old data file
									fclose($fp);
									// Open data file for output
									$fp = @fopen( $this->_dataFileName, "ab");
									if ($fp === false)
									{
										$this->setError("Could not open archive file {$this->_dataFileName} for append!");
										return false;
									}
								}
								$zdata = substr($sourceNameOrData, -$rest_size);
							}
							$bytes_left = $rest_size;
						} // end while
					}
				}
			}
			else
			{
				// IMPORTANT! Only this case can be spanned across steps: uncompressed, non-virtual data
				// Load cached data if we're resumming file packing
				if($this->processingFile)
				{
					$sourceNameOrData = $configuration->get('volatile.engine.archiver.sourceNameOrData', '');
					$unc_len = $configuration->get('volatile.engine.archiver.unc_len', 0);
					$resume = $configuration->get('volatile.engine.archiver.resume', 0);
				}

				// Copy the file contents, ignore directories
				$zdatafp = @fopen( $sourceNameOrData, "rb" );
				if( $zdatafp === FALSE )
				{
					$this->setWarning(sprintf('Unreadable file %s. Check permissions', $sourceNameOrData));
					return false;
				}
				else
				{
					// Seek to the resume point if required
					if($this->processingFile)
					{
						// Seek to new offset
						$seek_result = @fseek($zdatafp, $resume);
						if( $seek_result === -1 )
						{
							// What?! We can't resume!
							$this->setError(sprintf('Could not resume packing of file %s. Your archive is damaged!', $sourceNameOrData));
							return false;
						}

						// Doctor the uncompressed size to match the remainder of the data
						$unc_len = $unc_len - $resume;
					}

					if(!$this->_useSplitZIP)
					{
						while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
							$zdata = fread($zdatafp, AKEEBA_CHUNK);
							$this->_fwrite( $fp, $zdata, min(strlen($zdata), AKEEBA_CHUNK) );
							$unc_len -= AKEEBA_CHUNK;
							if($this->getError()) return;
						}
						if(!feof($zdatafp))
						{
							// We have to break, or we'll time out!
							$resume = @ftell($zdatafp);
							$configuration->set('volatile.engine.archiver.resume', $resume);
							$this->processingFile = true;
							return true;
						}
					}
					else
					{
						// Split ZIP - Do we have enough space to host the whole file?
						clearstatcache();
						$current_part_size = @filesize($this->_dataFileName);
						$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);
						if($free_space >= $unc_len)
						{
							// Yes, it will fit inside this part, do quick copy
							while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) && ($unc_len > 0) ) {
								$zdata = fread($zdatafp, AKEEBA_CHUNK);
								$this->_fwrite( $fp, $zdata, min(strlen($zdata), AKEEBA_CHUNK) );
								$unc_len -= AKEEBA_CHUNK;
								if($this->getError()) return;
							}
							if(!feof($zdatafp))
							{
								// We have to break, or we'll time out!
								$resume = @ftell($zdatafp);
								$configuration->set('volatile.engine.archiver.resume', $resume);
								$this->processingFile = true;
								return true;
							}
						}
						else
						{
							// No, we'll have to split between parts. We'll loop until we run
							// out of space.

							while( !feof($zdatafp) && ($timer->getTimeLeft() > 0) )
							{
								clearstatcache();
								$current_part_size = @filesize($this->_dataFileName);
								$free_space = $this->_fragmentSize - ($current_part_size === false ? 0 : $current_part_size);

								// Find optimal chunk size
								$chunk_size_primary = min(AKEEBA_CHUNK, $free_space);
								if($chunk_size_primary <= 0) $chunk_size_primary = max(AKEEBA_CHUNK, $free_space);
								// Calculate if we have to read some more data (smaller chunk size)
								// and how many times we must read w/ the primary chunk size
								$chunk_size_secondary = $free_space % $chunk_size_primary;
								$loop_times = ($free_space - $chunk_size_secondary) / $chunk_size_primary;
								// Read and write with the primary chunk size
								for( $i = 1; $i <= $loop_times; $i++ )
								{
									$zdata = fread($zdatafp, $chunk_size_primary);
									$this->_fwrite( $fp, $zdata, min(strlen($zdata), $chunk_size_primary) );
									$unc_len -= $chunk_size_primary;
									if($this->getError()) return;

									// Do we have enough time to proceed?
									if( (!feof($zdatafp)) && ($timer->getTimeLeft() <= 0) ) {
										// No, we have to break, or we'll time out!
										$resume = @ftell($zdatafp);
										$configuration->set('volatile.engine.archiver.resume', $resume);
										$this->processingFile = true;
										return true;
									}

								}

								// Read and write w/ secondary chunk size, if non-zero
								if($chunk_size_secondary > 0)
								{
									$zdata = fread($zdatafp, $chunk_size_secondary);
									$this->_fwrite( $fp, $zdata, min(strlen($zdata), $chunk_size_secondary) );
									$unc_len -= $chunk_size_secondary;
									if($this->getError()) return;
								}

								// Do we have enough time to proceed?
								if( (!feof($zdatafp)) && ($timer->getTimeLeft() <= 0) ) {
									// No, we have to break, or we'll time out!
									$resume = @ftell($zdatafp);
									$configuration->set('volatile.engine.archiver.resume', $resume);
									$this->processingFile = true;

									// ...and create a new part as well
									if(!$this->_createNewPart())
									{
										// Die if we couldn't create the new part
										$this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
										return false;
									}

									// ...then, return
									return true;
								}

								// Create new JPA part, but only if we'll have more data to write
								if(!feof($zdatafp) && ($unc_len > 0) )
								{
									if(!$this->_createNewPart())
									{
										// Die if we couldn't create the new part
										$this->setError('Could not create new JPA part file '.basename($this->_dataFileName));
										return false;
									}
									else
									{
										// Close the old data file
										fclose($fp);
										// Open data file for output
										$fp = @fopen( $this->_dataFileName, "ab");
										if ($fp === false)
										{
											$this->setError("Could not open archive file {$this->_dataFileName} for append!");
											return false;
										}
									}
								}
							} // end while
						}
					}
					fclose( $zdatafp );
				}
			}
		}
		elseif($isSymlink)
		{
			$this->_fwrite($fp, @readlink($sourceNameOrData) );
		}

		@fclose( $fp );

		//AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "-- Added $targetName to archive");

		// Uncache data
		$configuration->set('volatile.engine.archiver.sourceNameOrData', null);
		$configuration->set('volatile.engine.archiver.unc_len', null);
		$this->processingFile = false;

		// ... and return TRUE = success
		return TRUE;
	}


	// ------------------------------------------------------------------------
	// Archiver-specific utility functions
	// ------------------------------------------------------------------------
	/**
	* Outputs a Standard Header at the top of the file
	*
	*/
	private function _writeArchiveHeader()
	{
		$fp = @fopen( $this->_dataFileName, 'r+' );
		if($fp === false)
		{
			$this->setError('Could not open '.$this->_dataFileName.' for writing. Check permissions and open_basedir restrictions.');
			return;
		}

		// Calculate total header size
		$headerSize = 19; // Standard Header
		if($this->_useSplitZIP) $headerSize += 8; // Spanned JPA header

		$this->_fwrite( $fp, $this->_archive_signature );					// ID string (JPA)
		if($this->getError()) return;
		$this->_fwrite( $fp, pack('v', $headerSize) );					// Header length; fixed to 19 bytes
		$this->_fwrite( $fp, pack('C', _JPA_MAJOR ) );					// Major version
		$this->_fwrite( $fp, pack('C', _JPA_MINOR ) );					// Minor version
		$this->_fwrite( $fp, pack('V', $this->_fileCount ) );			// File count
		$this->_fwrite( $fp, pack('V', $this->_uncompressedSize ) );	// Size of files when extracted
		$this->_fwrite( $fp, pack('V', $this->_compressedSize ) );		// Size of files when stored

		// Do I need to add a split archive's header too?
		if($this->_useSplitZIP)
		{
			$this->_fwrite( $fp, $this->_extraHeaderSplit);				// Signature
			$this->_fwrite( $fp, pack('v', 4) );						// Extra field length
			$this->_fwrite( $fp, pack('v', $this->_totalFragments) );	// Number of parts
		}

		@fclose( $fp );
		if( function_exists('chmod') )
		{
			@chmod($this->_dataFileName, 0755);
		}
	}

	private function _createNewPart($finalPart = false)
	{
		$this->_totalFragments++;
		$this->_currentFragment = $this->_totalFragments;
		if($finalPart)
		{
			$this->_dataFileName = $this->_dataFileNameBase.'.jpa';
		}
		else
		{
			$this->_dataFileName = $this->_dataFileNameBase.'.j'.sprintf('%02d', $this->_currentFragment);
		}
		AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Creating new JPA part #'.$this->_currentFragment.', file '.$this->_dataFileName);
		// Inform that we have chenged the multipart number
		$statistics =& AEFactory::getStatistics();
		$statistics->updateMultipart($this->_totalFragments);
		// Try to remove any existing file
		@unlink($this->_dataFileName);
		// Touch the new file
		return @touch($this->_dataFileName);
	}

}