#!/bin/php
<?php
ini_set("memory_limit", "2048M");
if(empty($argv[1])){
    echo "Usage: diffget.php Package-Url\r\n";
    exit(1);
}
$url = $argv[1];
$pkg_srv=get_srv_pkg_info($url);
$pkg_loc = get_local_pkg_info($url);

$exp = explode("/",$pkg_srv["PATH"]);
unset($exp[count($exp)-1]);
$pkgname=join("/",$exp);
$srv_allmd5 = get_srv_pkg_allmd5($url,$pkgname);

echo "Need File from ".$pkg_srv["HOST"].":\r\n";
echo "MD5: ".$pkg_srv["MD5"]. " | ";
echo "SHA1: ".$pkg_srv["SHA1"]." | ";
echo "SIZE: ".substr("          ".$pkg_srv["SIZE"],-10)." | ";
echo "ME: none    | ";
echo "SRV: yes | ";
echo "PATH: ".$pkg_srv["PATH"]."\r\n";


// Search in snapshots for additional files...
$orig_havemd5=array();
foreach($pkg_loc as $pkg){
    $orig_havemd5[]=$pkg["MD5"];
}
search_more_files_in_snaps($url,$srv_allmd5,$orig_havemd5,$pkg_loc);
function search_more_files_in_snaps($url,$srv_allmd5,$orig_havemd5,&$pkg_loc){
    $snaps = scandir("/.snapshots");
    $lastsnaptime=0;
    foreach($snaps as $snapdir){
	if($snapdir=="." || $snapdir=="..") continue;
	$snaptime = filemtime("/.snapshots/".$snapdir."/info.xml");
	if($snaptime<($lastsnaptime+(60*60*24*14))){
	    // Snap must be 14 days ahead from last snap, skip...
	    continue;
	}
	$lastsnaptime=$snaptime;

	$path="/.snapshots/".$snapdir."/snapshot/var/cache/binpkgs/";
	$snapshot_pkgs = get_local_pkg_info_snap($url,$path."Packages");
	foreach($snapshot_pkgs as $snapshot_pkg){
	    if(in_array($snapshot_pkg["MD5"],$srv_allmd5) && !in_array($snapshot_pkg["MD5"],$orig_havemd5)){
		$snapshot_pkg["snapshot_id"]=$snapdir;
		$snapshot_pkg["snapshot_path"]=$path;
		$pkg_loc[]=$snapshot_pkg;
		$orig_havemd5[]=$snapshot_pkg["MD5"];
	    }
	}
    }
}

echo "Have Files:\r\n";
$havemd5=array();
foreach($pkg_loc as $pkg){
    echo "MD5: ".$pkg["MD5"]. " | ";
    echo "SHA1: ".$pkg["SHA1"]." | ";
    echo "SIZE: ".substr("          ".$pkg["SIZE"],-10)." | ";
    if(isset($pkg["snapshot_id"])){
	echo "ME: in snap | ";
    }
    else{
	echo "ME: yes     | ";
    }
    if(in_array($pkg["MD5"],$srv_allmd5)){
	$havemd5[]=$pkg["MD5"];
	echo "SRV: yes | ";
    }
    else{
	echo "SRV: no  | ";
    }
    echo "PATH: ".$pkg["PATH"];
    if(isset($pkg["snapshot_id"])){
	echo " (Snapshot: ".$pkg["snapshot_id"].")";
    }
    echo "\r\n";
}

if(in_array($pkg_srv["MD5"],$havemd5)){
    echo "Package already available! DONE!\r\n";
    echo "Search for MD5 ".$pkg_srv["MD5"]."...:\r\n";
    echo "- Server MTIME: ".$pkg_srv["MTIME"]."\r\n";
    $loctimes=array();
    foreach($pkg_loc as $pkg) {
        if($pkg["MD5"]==$pkg_srv["MD5"]){
            echo "- Local  MTIME: ".$pkg["MTIME"]."\r\n";
            $loctimes[]=$pkg["MTIME"];
        }
    }
    if(in_array($pkg_srv["MTIME"],$loctimes)){
        echo "Same MTIMES found - DIE :(\r\n";
        exit(0);
    }
    else{
        echo "MTIMES differs - repeat...\r\n";
    }
}
if(count($havemd5)<1){
    echo "No same packages on srv and local! DONE!\r\n";
    exit(0);
}


if(true){
    echo date("d.m.Y H:i:s")." Calling Server:\r\n";
    $url = "https://".$pkg_srv["HOST"]."/diffget/diffget_server.php?pkg=".$pkg_srv["PKG"]."&need_md5=".$pkg_srv["MD5"]."&have_md5=".join(",",$havemd5);
    echo $url."\r\n";
    $context = stream_context_create(array("http"=>array("timeout"=>3600)));
    $answer = @file_get_contents($url,false,$context);
    echo "Server overhead: ".strlen($answer)."\r\n";
    echo $answer;
    echo "END OF SERVER ANSWER!\r\n";
    echo "\r\n\r\n";
    $answer_lines = explode("\n",$answer);
    $downloadpath="";
    foreach($answer_lines as $line){
	$line=trim($line);
	if(substr($line,0,26)=="Filepath for download is: "){
	    $downloadpath=substr($line,26);
	}
    }
    if(empty($downloadpath)){
	echo date("d.m.Y H:i:s")." Can't get download path :(\r\n";
	exit(1);
    }
    echo "Downloadpath: "."https://".$pkg_srv["HOST"]."/diffget/".$downloadpath."\r\n";
    $fulldownloadpath = "https://".$pkg_srv["HOST"]."/diffget/".$downloadpath;
    $content = file_get_contents("https://".$pkg_srv["HOST"]."/diffget/".$downloadpath);
}
echo "Transferred Data Size: ".strlen($content)."\r\n";
$content = gzuncompress($content);
$content = unserialize($content);


// Recreate file
if(count($content)<8){
    echo "Delivered content invalid :(\r\n";
    exit(1);
}
else{
    $compression="zstd";
    $difftype="bsdiff";
    if($content["filetype"]>3){
	echo "Filetype ".$content["filetype"]." not understood from us :(\r\n";
	exit(1);
    }
    if($content["filetype"]>=2){
	$compression=$content["compression"];
    }
    if($content["filetype"]==3){
	$difftype=$content["difftype"];
    }
    echo "We must create file (type: ".$content["filetype"].")...\r\n";
    $tmpdir="/tmp/diffget/".md5($content["server_path"]);
    @mkdir("/tmp/diffget");
    if(file_exists($tmpdir) && is_dir($tmpdir)){
	clean_dir($tmpdir);
    }
    @mkdir($tmpdir);
    chdir($tmpdir);

    $cnt=0;
    foreach($content["order_md5"] as $md5){
	$have_pkg=array();
	foreach($pkg_loc as $tmppkg){
	    if($tmppkg["MD5"]==$md5){
		$have_pkg=$tmppkg;
	    }
	}
	if(count($have_pkg)<1){
	    echo "Dont find package for ".$md5." :(\r\n";
	    exit(1);
	}

	$cnt++;
	if(isset($have_pkg["snapshot_path"])){
	    copy($have_pkg["snapshot_path"].$have_pkg["PATH"],$tmpdir."/have.".$cnt.".gpkg.tar");
	}
	else{
	    copy("/var/cache/binpkgs/".$have_pkg["PATH"],$tmpdir."/have.".$cnt.".gpkg.tar");
	}
	
	@mkdir("have.".$cnt.".gpkg");
	chdir("have.".$cnt.".gpkg");
	echo shell_exec("tar --strip-components=1 -xf ../have.".$cnt.".gpkg.tar 2>&1");
	if(file_exists("image.tar.zst")){
	    @unlink("image.tar");
	    echo shell_exec("zstd -d image.tar.zst 2>&1");
	}
	elseif(file_exists("image.tar.xz")){
	    @unlink("image.tar");
	    echo shell_exec("xz -k -d image.tar.xz 2>&1");
	}
	else{
	    echo "have.".$cnt." - image.tar.zst/.xz not found :(\r\n";
	    exit(1);
	}
	echo shell_exec("cat image.tar >> ../have.image.tar");
	chdir("..");
    }
    
    $fp=fopen("delta.image.tar.diff","w");
    fwrite($fp,$content["patch.image"]);
    fclose($fp);
    
    if($compression=="zstd"){
	$fp=fopen("delta.image.tar.zst.diff","w");
	fwrite($fp,$content["patch.image.zst"]);
	fclose($fp);
    }
    elseif($compression=="xz"){
	$fp=fopen("delta.image.tar.xz.diff","w");
	fwrite($fp,$content["patch.image.xz"]);
	fclose($fp);
    }
    
    @unlink("need.image.temp.tar");
    @unlink("need.image.temp.tar.zst");
    @unlink("need.image.temp.tar.xz");
    if($difftype=="bsdiff"){
	echo "Calling bspatch...\r\n";
	echo shell_exec("bspatch have.image.tar need.image.temp.tar delta.image.tar.diff 2>&1");
    }
    elseif($difftype=="xdelta"){
	echo "Calling xdelta...\r\n";
	echo shell_exec("xdelta3 -D -R -d -s have.image.tar delta.image.tar.diff need.image.temp.tar 2>&1");
    }
    elseif($difftype=="xdelta+xz"){
	echo "Calling xdelta+xz...\r\n";
	rename("delta.image.tar.diff","delta.image.tar.diff.xz");
	echo shell_exec("xz -d delta.image.tar.diff.xz 2>&1");
	echo shell_exec("xdelta3 -D -R -d -s have.image.tar delta.image.tar.diff need.image.temp.tar 2>&1");
    }
    else{
	echo "Unknown difftype ".$difftype." :(\r\n";
	exit(1);
    }
    
    if($compression=="zstd"){
	echo "Calling zstd...\r\n";
	echo shell_exec("zstd need.image.temp.tar 2>&1");
	if($difftype=="bsdiff"){
	    echo "Calling bspatch2...\r\n";
	    echo shell_exec("bspatch need.image.temp.tar.zst need.image.tar.zst delta.image.tar.zst.diff 2>&1");
	}
	elseif($difftype=="xdelta"){
	    echo "Calling xdelta2...\r\n";
	    echo shell_exec("xdelta3 -D -R -d -s need.image.temp.tar.zst delta.image.tar.zst.diff need.image.tar.zst 2>&1");
	}
	elseif($difftype=="xdelta+xz"){
	    echo "Calling xdelta+xz2...\r\n";
	    rename("delta.image.tar.zst.diff","delta.image.tar.zst.diff.xz");
	    echo shell_exec("xz -d delta.image.tar.zst.diff.xz 2>&1");
	    echo shell_exec("xdelta3 -D -R -d -s need.image.temp.tar.zst delta.image.tar.zst.diff need.image.tar.zst 2>&1");
	}
	else{
	    echo "Unknown difftype ".$difftype." :(\r\n";
	    exit(1);
	}
    }
    elseif($compression=="xz"){
	echo "Calling xz...\r\n";
	echo shell_exec("xz -k need.image.temp.tar 2>&1");
	if($difftype=="bsdiff"){
	    echo "Calling bspatch2...\r\n";
	    echo shell_exec("bspatch need.image.temp.tar.xz need.image.tar.xz delta.image.tar.xz.diff 2>&1");
	}
	elseif($difftype=="xdelta"){
	    echo "Calling xdelta2...\r\n";
	    echo shell_exec("xdelta3 -D -R -d -s need.image.temp.tar.xz delta.image.tar.xz.diff need.image.tar.xz 2>&1");
	}
	elseif($difftype=="xdelta+xz"){
	    echo "Calling xdelta+xz2...\r\n";
	    rename("delta.image.tar.xz.diff","delta.image.tar.xz.diff.xz");
	    echo shell_exec("xz -d delta.image.tar.xz.diff.xz 2>&1");
	    echo shell_exec("xdelta3 -D -R -d -s need.image.temp.tar.xz delta.image.tar.xz.diff need.image.tar.xz 2>&1");
	}
	else{
	    echo "Unknown difftype ".$difftype." :(\r\n";
	    exit(1);
	}
    }

    $fp = fopen("newpkg.gpkg.tar","w");
    fwrite($fp,$content["gpkg_pre"]);
    if($compression=="zstd")	fwrite($fp,file_get_contents("need.image.tar.zst"));
    if($compression=="xz")	fwrite($fp,file_get_contents("need.image.tar.xz"));
    fwrite($fp,$content["gpkg_post"]);
    fclose($fp);
    
    if(md5_file("newpkg.gpkg.tar")!=$pkg_srv["MD5"]){
	echo "MD5 mismatch :(\r\n";
	exit(1);
    }
    else{
	echo "MD5 match :)\r\n";
	rename("newpkg.gpkg.tar",$content["dir_in_tar"].".gpkg.tar");
	touch($content["dir_in_tar"].".gpkg.tar",$content["filemtime"]);
    }
    echo "Submitting file to binpkgdir...\r\n";
    $tfile = "";
    $ok=false;
    for($i=0;$i<100;$i++){
	$number=trim($i);
	if($number=="0")$number="";
	$tfile = "/var/cache/binpkgs/".$pkg_srv["PKG"]."/".$content["dir_in_tar"].$number.".gpkg.tar";
	if(file_exists($tfile) && filesize($tfile)>0){
	    // found -> next...
	}
	else{
	    $ok=true;
	    break;
	}
    }
    if(!$ok){
	echo "File ".$tfile." already exists :(\r\n";
	exit(1);
    }
    copy($content["dir_in_tar"].".gpkg.tar",$tfile);
    touch($tfile,$content["filemtime"]);
    echo "Successfully delivered ".$tfile."!\r\n";
    echo "Calling \"emaint -f binhost\" outstanding!\r\n";
    echo "Cleanup...\r\n";
    clean_dir("/tmp/diffget");
}
echo "DONE!\r\n";
exit(0);


function get_local_pkg_info($url){
    $parse = parse_url($url);
    if(empty($parse["path"])){
	echo "ERROR: Can't parse path from url :(\r\n";
	exit(1);
    }

    $path = explode("/",$parse["path"]);
    $path_cat = $path[count($path)-3];
    $path_pkg = $path[count($path)-2];
    $path_fil = $path[count($path)-1];

    $local_package = "/var/cache/binpkgs/Packages";
    $loc_pkg = parse_packages($local_package);

    $found_loc_pkgs=array();
    foreach($loc_pkg as $pkg){
	$search_path=$path_cat."/".$path_pkg."/";
	if(substr($pkg["PATH"],0,strlen($search_path))==$search_path){
	    $found_loc_pkgs[]=$pkg;
	}
    }

    if(count($found_loc_pkgs)<1){
	echo "We dont have a local package - must do a complete download :(\r\n";
	exit(2);
    }


    $relinfos=array();
    foreach($found_loc_pkgs as $pkg){
	$relinfo=array();
	$relinfo["PATH"]=$pkg["PATH"];
	$relinfo["MD5"]=$pkg["MD5"];
	$relinfo["SHA1"]=$pkg["SHA1"];
    $relinfo["SIZE"]=$pkg["SIZE"];
    $relinfo["MTIME"]=$pkg["MTIME"];
	$relinfos[]=$relinfo;
    }

    return($relinfos);
}

function get_local_pkg_info_snap($url,$pkg_file){
    $parse = parse_url($url);
    if(empty($parse["path"])){
	echo "ERROR: Can't parse path from url :(\r\n";
	exit(1);
    }

    $path = explode("/",$parse["path"]);
    $path_cat = $path[count($path)-3];
    $path_pkg = $path[count($path)-2];
    $path_fil = $path[count($path)-1];

    $loc_pkg = parse_packages($pkg_file);

    $found_loc_pkgs=array();
    foreach($loc_pkg as $pkg){
	$search_path=$path_cat."/".$path_pkg."/";
	if(substr($pkg["PATH"],0,strlen($search_path))==$search_path){
	    $found_loc_pkgs[]=$pkg;
	}
    }

    $relinfos=array();
    foreach($found_loc_pkgs as $pkg){
	$relinfo=array();
	$relinfo["PATH"]=$pkg["PATH"];
	$relinfo["MD5"]=$pkg["MD5"];
	$relinfo["SHA1"]=$pkg["SHA1"];
    $relinfo["SIZE"]=$pkg["SIZE"];
    $relinfo["MTIME"]=$pkg["MTIME"];
	$relinfos[]=$relinfo;
    }

    return($relinfos);
}

function get_srv_pkg_info($url){
    $parse = parse_url($url);
    $host = $parse["host"];

    if(empty($host)){
	echo "ERROR: Can't parse host from url :(\r\n";
	exit(1);
    }

    $path = explode("/",$parse["path"]);
    $path_cat = $path[count($path)-3];
    $path_pkg = $path[count($path)-2];
    $path_fil = $path[count($path)-1];

    $server_package = "/var/cache/edb/binhost/".$host."/packages/Packages";
    $srv_pkg = parse_packages($server_package);

    $found_srv_pkg=array();
    foreach($srv_pkg as $pkg){
	if($pkg["PATH"]==$path_cat."/".$path_pkg."/".$path_fil){
	    $found_srv_pkg=$pkg;
	}
    }

    if(count($found_srv_pkg)<1){
	echo "ERROR: Can't find package on binhost :(\r\n";
	exit(1);
    }

    $relinfo=array();
    $relinfo["PKG"]=$path_cat."/".$path_pkg;
    $relinfo["PATH"]=$found_srv_pkg["PATH"];
    $relinfo["BUILD_ID"]=$found_srv_pkg["BUILD_ID"];
    $relinfo["CPV"]=$found_srv_pkg["CPV"];
    $relinfo["MD5"]=$found_srv_pkg["MD5"];
    $relinfo["SHA1"]=$found_srv_pkg["SHA1"];
    $relinfo["SIZE"]=$found_srv_pkg["SIZE"];
    $relinfo["MTIME"]=$found_srv_pkg["MTIME"];
    $relinfo["HOST"]=$host;

    return($relinfo);
}

function parse_packages($pkg_file){
    $srv_packages = file($pkg_file);
    $cur_pkg=array();
    $all_pkg=array();
    foreach($srv_packages as $line){
	$line=trim($line);
	if(empty($line)){
	    if(isset($cur_pkg["PATH"])){
		$all_pkg[]=$cur_pkg;
	    }
	    $cur_pkg=array();
	    continue;
	}
	$pos=strpos($line,":");
	$key=trim(substr($line,0,$pos));
	$val=trim(substr($line,$pos+1));
	$cur_pkg[$key]=$val;
    }
    return($all_pkg);
}
function clean_dir($dir){
    $exp = explode("/",$dir);
    if(count($exp)<2){
	echo "Cant clean_dir(".$dir.") :(\r\n";
	exit(1);
    }
    $scan = scandir($dir);
    foreach($scan as $file){
	if($file=="." || $file=="..")continue;
	if(is_dir($dir."/".$file)){
	    clean_dir($dir."/".$file);
	    rmdir($dir."/".$file);
	}
	else{
	    unlink($dir."/".$file);
	}
    }
}
function get_srv_pkg_allmd5($url,$pkgname){
    $parse = parse_url($url);
    $host=$parse["host"];

    $server_package = "/var/cache/edb/binhost/".$host."/packages/Packages";
    $srv_pkg = parse_packages($server_package);

    $found_md5s=array();
    foreach($srv_pkg as $pkg){
	if(substr($pkg["PATH"],0,strlen($pkgname))==$pkgname){
	    $found_md5s[]=$pkg["MD5"];
	}
    }
    return($found_md5s);
}


function possibilities($arr){
    $words = $arr;
    $num = count($words); 
    
    $possibilities=array();
    $thisposs=array();

    //The total number of possible combinations 
    $total = pow(2, $num);

    //Loop through each possible combination  
    for ($i = 0; $i < $total; $i++) {  
	//For each combination check if each bit is set 
	for ($j = 0; $j < $num; $j++) { 
	    //Is bit $j set in $i? 
	    if (pow(2, $j) & $i){
		$thisposs[]=$words[$j];
	    } 
	} 
	$possibilities[]=$thisposs;
	$thisposs=array();
    }
    return($possibilities);
}

/**
 * Returns the size of a file without downloading it, or -1 if the file
 * size could not be determined.
 *
 * @param $url - The location of the remote file to download. Cannot
 * be null or empty.
 *
 * @return The size of the file referenced by $url, or -1 if the size
 * could not be determined.
 */
 /*
function curl_get_file_size( $url ) {
  // Assume failure.
  $result = -1;

  $curl = curl_init( $url );

  // Issue a HEAD request and follow any redirects.
  curl_setopt( $curl, CURLOPT_NOBODY, true );
  curl_setopt( $curl, CURLOPT_HEADER, true );
  curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
  #curl_setopt( $curl, CURLOPT_FOLLOWLOCATION, true );

  $data = curl_exec( $curl );
  if(curl_getinfo($curl,CURLINFO_RESPONSE_CODE)==200){
    $size = curl_getinfo($curl,CURLINFO_CONTENT_LENGTH_DOWNLOAD);
  }
  else{
    $size=-1;
  }
  
  curl_close( $curl );
  return($size);
}

*/
