Location: PHPKode > scripts > BinPacker > binpacker/class-bp.php
<?php
/**
* Bin Packer Class
* See readme.txt file for description and usage
* 2004-12-19	First Version	hide@address.com
*
* IMPORTANT NOTE
* there is no warranty, implied or otherwise with this software.
* 
* LICENCE
* This code has been placed in the Public Domain for all to enjoy.
*
* @author	<hide@address.com>
* @package	BinPacker
*/
class BinPacker {

	// Private Variables
	private $debug;
	private $binSize;
	private $pointerArray;
	private $hashIndex;
	private $totalCargo;
	private $bins;

	// Class Constructor
	public function __construct($debugMode=false) {
		$this->debug=$debugMode;
		$this->pointerArray=array();
		$this->hashIndex=array();
		$this->totalCargo=0;
		$this->bins=array();
		mt_srand(doubleval(microtime())*100000000);
		$this->dprint("Class BinPacker Created!");
	}

	// Class Destructor
	public function __destruct() {
		$this->dprint("Class BinPacker Destroyed!");
	}

	// Debug Print
	private function dprint($string="") {
		if($this->debug){
			print("$string\n");
		}
	}

	// Setting Bin Size
	public function setBinSize($size=690,$metric="M") {
		$multiplier=1;
		switch(strtolower($metric)){
			case 'k':
				$multiplier=1024;
				break;
			case 'm':
				$multiplier=1024*1024;
				break;
			case 'g':
				$multiplier=1024*1024*1024;
				break;
			default:
				$multiplier=1;
		}
		$this->binSize=$size*$multiplier;
		$this->dprint("Bin Size Set to $size$metric");
	}

	// Return Bin Size
	public function getBinSize() {
		return $this->binSize;
	}

	// Recursive Function to Add Directories/Files
	public function add($node,$recursive=false,$extension="") {

		$this->dprint("Adding $node");

		$node=str_replace('\\','/',$node);

		if(is_dir($node)){

			$currentDirectory=$node;

			while(substr($currentDirectory,strlen($currentDirectory)-1,1)=="/"){
				$currentDirectory=substr($currentDirectory,0,strlen($currentDirectory)-1);
			}

			$directory=opendir($currentDirectory);
			while(FALSE !== ($newNode=readdir($directory))){
				if($newNode!="." && $newNode!=".."){
					$nodePath="$currentDirectory/$newNode";
					if(is_dir($nodePath) && $recursive){
						$this->add($nodePath,$recursive,$extension);
					}

					$extDot=strrpos($newNode,'.');
					if($extDot){
						$fileExt=strtolower(substr($newNode,$extDot+1,strlen($newNode)-$extDot-1));
					}

					if(strlen($extension)>0) {
					       	if(strtolower($extension)==$fileExt){
							if(is_file($nodePath)) $this->add($nodePath);
						}
					} else {
						if(is_file($nodePath)) $this->add($nodePath);
					}
				}
			}
			closedir($directory);
		}

		if(is_file($node)){
			$fileSize=filesize($node);

			if($fileSize>$this->binSize){
				$this->dprint("Skipping $node - bigger than bin!");
			} else {
				$fuzzyHash=$this->getFuzzyHash($node);
				if(!array_key_exists($fuzzyHash,$this->hashIndex)){
					$this->hashIndex[$fuzzyHash]=array($node,$fileSize,0);
					$this->pointerArray[]=$fuzzyHash;
					$this->totalCargo=$this->totalCargo+$fileSize;
				} else {
					$this->dprint("Skipping $node - already exists!");
				}
			}
		}
	}

	// Return 'Fuzzy' Hash
	private function getFuzzyHash($filePath) {
		$fp=fopen($filePath,"r");
		$currentHash=sha1(fread($fp,1048576));
		while(!feof($fp)) $currentHash=sha1($currentHash.sha1(fread($fp,1048576)));
		fclose($fp);
		return $currentHash;
	}

	// Pack Function	
	public function pack() {
		$this->dprint("Packing...");
		$totalBins=ceil($this->totalCargo/$this->binSize);
		$this->dprint("Ideally $totalBins Bin(s)...");
		$totalItems=count($this->pointerArray);
		if($totalItems>1){
			$this->shuffle();
			$binID=0;
			while($this->packRemaining()>0){
				$currentBin=array();
				$currentBinTotal=0;
				for($i=0;$i<$totalItems;$i++){
					$currentItem=$this->hashIndex[$this->pointerArray[$i]];
					if(($currentItem[2]==0) && (($currentItem[1]+$currentBinTotal)<=$this->binSize)){
						$this->hashIndex[$this->pointerArray[$i]][2]=1;
						$currentBinTotal+=$currentItem[1];
						$currentBin[]=$i;
					}
				}
				$this->bins[]=$currentBin;
				$binID++;
			}
		} else {
			$this->dprint("Packing: task too trivial... more than 1 file is required!");
		}
	}

	// Output Function
	public function output($targetDir="",$move=false) {
		$moveFiles=false;
		if(strlen($targetDir)>0){
			if(is_dir($targetDir)){
				$moveFiles=true;
			}
		}

		$binNumber=1;
		foreach ($this->bins as $itemKey=>$itemValue){
			$fileArray=array();
			$binSize=0;
			foreach($itemValue as $key=>$value){
				$myItem=$this->hashIndex[$this->pointerArray[$value]];
				$fileArray[]=$myItem[0];
				$binSize+=$myItem[1];
				$filePath=$myItem[0];
				if($moveFiles) $this->moveFile($filePath,$targetDir,$binNumber,$move);	
			}
			sort($fileArray);
			$binWaste=$this->binSize-$binSize;
			$this->dprint("Bin $binNumber : Size = $binSize ($binWaste Bytes Wasted)");
			$binNumber++;
		}
	}

	// Copy/Move File, Creating subfolders and info files
	private function moveFile($filePath,$targetDir,$binNumber,$move=false) {
		
		while(substr($targetDir,strlen($targetDir)-1,1)=="/"){
			$targetDir=substr($targetDir,0,strlen($targetDir)-1);
		}

		if(!is_dir("$targetDir/bin-$binNumber")){
			mkdir("$targetDir/bin-$binNumber");
		}

		$logFile="$targetDir/bin-$binNumber.txt";
		$targetDir="$targetDir/bin-$binNumber";

		str_replace("\\","/",$filePath);
		$pathArray=split('/',$filePath);

		if(count($pathArray)>1) {
			array_pop($pathArray);
		} else {
			$pathArray=array();
		}

		$logPath='';
		foreach($pathArray as $key=>$value){
			if((strlen($value)>0) && (strpos($value,':')==false)){
				$targetDir="$targetDir/$value";
				if(!is_dir($targetDir)){
					mkdir($targetDir);
				}
				$logPath="$logPath/$value";
			}
		}

		if(!is_file("$targetDir/".basename($filePath))){
			if($move){
				rename($filePath,"$targetDir/".basename($filePath));
			} else {
				copy($filePath,"$targetDir/".basename($filePath));
			}
			if (!$handle = fopen($logFile, 'a')) { echo "Cannot open file ($logFile)"; }
			if (fwrite($handle, "$logPath/".basename($filePath)."\r\n") === FALSE) { echo "Cannot write to file ($logFile)"; }
			fclose($handle);
		} else {
			$this->dprint("Target Directory already contains file!");
		}
	}

	// Return number of items that still need processing	
	private function packRemaining(){
		$remaining=0;
		$totalItems=count($this->pointerArray);
		for($i=0;$i<$totalItems;$i++){
			$currentItem=$this->hashIndex[$this->pointerArray[$i]];
			if($currentItem[2]==0) $remaining++;
		}
		return $remaining;
	}

	// Function to shuffle items	
	private function shuffle() {
		$lastIndex=count($this->pointerArray)-1;
		$iterations=($lastIndex+1)*1000;
		while($iterations>0){
			$i=0;$j=0;
			while($i==$j){$i=mt_rand(0,$lastIndex);$j=mt_rand(0,$lastIndex);}
			$z=$this->pointerArray[$i];
			$this->pointerArray[$i]=$this->pointerArray[$j];
			$this->pointerArray[$j]=$z;
			$iterations--;
		}
	}

}

?>
Return current item: BinPacker