Pandora pandora repo/website/native app-manager development


after looking at rockthesmufs code and wizardsstans original code I came up with this.

Code:
//search for .pnd filetype in current dir
$filetype = "pnd";
$dir = opendir('.');
while (false !== ($file = readdir($dir))) {
	$pnd = pathinfo($file);
	//if filetype is pnd then create variable name of pnd archive
	if($pnd['extension']==$filetype){
		$pndfile[] = $file;
	}
}
closedir($dir);

$pndnum = sizeof($pndfile);
echo "there are $pndnum .pnd files in the current directory<br/><br/>";

//for each pnd found return xml
foreach($pndfile as $val){
	echo "$val<br/>The contents of the xml are as follows:<br/>";

	//use function to pull pxml
	echo pullPXMLdata($val);	
	echo "<br/><br/>";
}
function pullPXMLdata($myFile){

	$fh = fopen($myFile, 'rb');
	$data = fread($fh, filesize($myFile));
	$foundPXML = 0;
	
	while($foundPXML<1){
		$pxml_start = strpos($data, "<PXML");
		$pxml_end = strpos($data, "</PXML>")+7;
		if($pxml_start !== false){$foundPXML=1;}
	}
	fseek($fh, $pxml_start);
	$data = fread($fh, $pxml_end - $pxml_start);
	fclose($fh);
	
	return htmlspecialchars($data);
}

output

getting closer now just need the icon and the screenshot image from the pnd and im good to go :)
anyone know of a way to achieve this please let me know thanks.

im not sure if strpos() is case sensitive yet so if it is I might alter it to look more like rockthesmurfs code.
 
milkshake said:
after looking at rockthesmufs code and wizardsstans original code I came up with this.

getting closer now just need the icon and the screenshot image from the pnd and im good to go :)
anyone know of a way to achieve this please let me know thanks.

im not sure if strpos() is case sensitive yet so if it is I might alter it to look more like rockthesmurfs code.

FYI there is an stripos function; the reason I went for a manual approach is I do not know enough about PHP, and I wasn't sure if this functions would cope when dealing with that is essentially binary data.

Steve
 
Last edited by a moderator:
ok I added the function to search 1000bytes at a time untill we find the start of the pxml :)

Code:
//search for .pnd filetype in current dir
$filetype = "pnd";
$dir = opendir('.');
while (false !== ($file = readdir($dir))) {
	$pnd = pathinfo($file);
	//if filetype is pnd then create variable name of pnd archive
	if($pnd['extension']==$filetype){
		$pndfile[] = $file;
	}
}
closedir($dir);

$pndnum = sizeof($pndfile);
echo "there are $pndnum .pnd files in the current directory<br/><br/>";

//for each pnd found return xml
foreach($pndfile as $val){
	echo "$val<br/>";

	//use function to pull pxml
	echo pullPXMLdata($val);	
	echo "<br/><br/>";
}
function pullPXMLdata($myFile){

	$fh = fopen($myFile, 'rb');
	$filesize=filesize($myFile);
	$foundPXML = 0;
	$toRead=1000;
	$seek-=1000;
	
	echo "file size: $filesize<br/>";
	
	while($foundPXML<1){
		$seekpoint=$filesize+$seek;
		//move file pointer to $seekpoint
		fseek($fh,$seekpoint);
		$data=fread($fh,$toRead);
		$pxml_start = strpos($data, "<PXML");
		//if we find the pxml startpoint then collect seekpoint for start and end of pxml
		if($pxml_start !== false){
			$pxml_end = strpos($data, "</PXML>")+7;
			$pxml_start+=$seekpoint;
			$pxml_end +=$seekpoint;
			$foundPXML=1;
		}
		echo "searching $seekpoint to $filesize now <br/>";
		//add more bytes to search then loop back through and search
		$seek-=1000;
		$toRead+=1000;
	}
	echo "Our PXML data is store between bytes $pxml_start and $pxml_end <br/><br/>";
	fseek($fh, $pxml_start);
	$data = fread($fh, $pxml_end - $pxml_start);
	fclose($fh);
	
	return htmlspecialchars($data);
}

thanks for the info rockthesmurf
 
milkshake said:
ok I added the function to search 1000bytes at a time untill we find the start of the pxml :)

Code:

thanks for the info rockthesmurf

Again, I don't know PHP, but the following code snippet gives me the PXML data and displays the icon if it finds one (it works for Pandora Panic):

Code:
<html>
 <head>
  <title>PHP Test</title>
 </head>
 <body>

 <?php 

$filename = "/home/gamesdev/public_html/pandora/pandorapanic.pnd";
$icon     = "pandorapanic_icon.png";

$readBufferSize = 2048;
$fileSize = filesize( $filename );
$read = 0;

$handle = fopen($filename, "rb");

$begin = -1;
$end = -1;

while ( $read < $fileSize )
{
    $toRead = $readBufferSize;
    $pos = $fileSize - $read - $readBufferSize;
    if ( $pos < 0 )
    {
        $toRead += $pos;
        $pos = 0;
    }
    fseek( $handle, $pos );
    $data = fread( $handle, $toRead );
    $read += $toRead;
    if ( $end == -1 )
    {
        for ( $i = 0; $i < $toRead - 6; $i += 1 )
        {
            if (   $data[ $i ] == '<' &&
                   $data[ $i + 1 ] == '/' &&
                 ( $data[ $i + 2 ] == 'P' || $data[ $i + 2 ] == 'p' ) &&
                 ( $data[ $i + 3 ] == 'X' || $data[ $i + 3 ] == 'x' ) &&
                 ( $data[ $i + 4 ] == 'M' || $data[ $i + 4 ] == 'm' ) &&
                 ( $data[ $i + 5 ] == 'L' || $data[ $i + 5 ] == 'l' ) &&
                   $data[ $i + 6 ] == '>' )
            {
                $end = $pos + $i + 7;
                break;
            }
        }
    }
    for ( $i = 0; $i < $toRead - 4; $i += 1 )
    { 
        if (   $data[ $i ] == '<' &&
             ( $data[ $i + 1 ] == 'P' || $data[ $i + 1 ] == 'p' ) &&
             ( $data[ $i + 2 ] == 'X' || $data[ $i + 2 ] == 'x' ) &&
             ( $data[ $i + 3 ] == 'M' || $data[ $i + 3 ] == 'm' ) &&
             ( $data[ $i + 4 ] == 'L' || $data[ $i + 4 ] == 'l' ) )
        {
            $begin = $pos + $i;
            break;
        }
    }
    if ( $begin != -1 && $end != -1 )
    {
        fseek( $handle, $begin );
        $data = fread( $handle, $end - $begin );
        echo "<p>" . htmlspecialchars( $data ) . "</p>";
        break;
    }
}

if ( $begin == -1 || $end == -1 )
{
    echo '<p>Not found</p>';
}

$iconAvailable = file_exists( $icon );

if ( ! $iconAvailable )
{
    fseek( $handle, $end );
    $data = fread( $handle, $fileSize - $end );
    for ( $i = 0; $i < $fileSize - $end - 3; $i += 1 )
    {
        if ( $data[ $i + 0 ] == chr( 0x89 ) && $data[ $i + 1 ] == chr( 0x50 ) && $data[ $i + 2 ] == chr( 0x4e ) && $data[ $i + 3 ] == chr( 0x47 ) )
        {
            echo '<p>Found PNG</p>';
            $writeHandle = fopen( $icon, "wb" );
            fwrite( $writeHandle, substr( $data, $i ) );
            fclose( $writeHandle );
            $iconAvailable = true;
            break;
        }      
    }
}

fclose( $handle );

if ( $iconAvailable )
{
    echo '[img]' . $icon . '[/img]';
}

?> 
 </body>
</html>

I should note it will look for the icon in the current directory, if it doesn't find it it'll attempt to get it from the PND and then place it in the current directory, so future times the page is loaded it will already have it. I haven't put any decent error checking in the code above, and haven't made an attempt at handling cases where two people load the PHP file at exactly the same time and both attempt to write to the PNG etc. etc. it is really just a little proof of concept for what you were asking about.

Steve
 
Last edited by a moderator:
its a bit messy but with bits of code merged in from everyone its starting to work.

Code:
//search for .pnd filetype in current dir
$filetype = "pnd";
$dir = opendir('.');
while (false !== ($file = readdir($dir))) {
	$pnd = pathinfo($file);
	//if filetype is pnd then create variable name of pnd archive
	if($pnd['extension']==$filetype){
		$pndfile[] = $file;
	}
}
closedir($dir);

$pndnum = sizeof($pndfile);
echo "there are $pndnum .pnd files in the current directory<br/><br/>";

//for each pnd found return xml
foreach($pndfile as $val){
	echo "$val<br/>";
	//use function to pull pxml
	$xml_string = pullPXMLdata($val);
	$xml_end = $xml_string[1];
	echo $xml_string[0];
	$icon_name = pngName($xml_string[2]);
	$icon = pullPng($val, $xml_end, $icon_name);
	echo "<br/><br/>";
}
function pullPXMLdata($myFile){

	$fh = fopen($myFile, 'rb');
	//$data = fread($fh, filesize($myFile));
	$filesize=filesize($myFile);
	$foundPXML = 0;
	$toRead=1000;
	$seek-=1000;
	
	echo "file size: $filesize<br/>";
	
	while($foundPXML<1){
		$seekpoint=$filesize+$seek;
		//move file pointer to $seekpoint
		fseek($fh,$seekpoint);
		$data=fread($fh,$toRead);
		$pxml_start = stripos($data, "<PXML");
		//if we find the pxml startpoint then collect seekpoint for start and end of pxml
		if($pxml_start !== false){
			$pxml_end = stripos($data, "</PXML>")+7;
			$pxml_start+=$seekpoint;
			$pxml_end +=$seekpoint;
			$foundPXML=1;
		}
		echo "searching $seekpoint to $filesize now <br/>";
		//add more bytes to search then loop back through and search
		$seek-=1000;
		$toRead+=1000;
	}
	echo "Our PXML data is store between bytes $pxml_start and $pxml_end <br/><br/>";
	fseek($fh, $pxml_start);
	$data = fread($fh, $pxml_end - $pxml_start);
	fclose($fh);
	
	$xml_Png[] = htmlspecialchars($data);
	$xml_Png[] = $pxml_end;
	$xml_Png[] = $data;
	
	return $xml_Png;
}
function pngName($xmlStr){
	$start_pos = stripos($xmlStr, '<icon src=');
	$final_pos = stripos($xmlStr, '"/>', $start_pos);
	//$str_pos -= (strlen($xmlStr)-$start_pos)+1;
		
	$length = $final_pos-$start_pos;
	$icon_name = substr($xmlStr,$start_pos,$length);
	
	$success = 0;
	$newString = strlen($icon_name);
	$search_pos = $newString-1;
	$slen = 0;

	while($success<1){
		//echo substr($xmlStr,$start_pos,$length)."hhhhh";
		$check = substr($icon_name,$search_pos,$slen);
		if($check=='/' || $check=='"'){
			$search_pos+=2;
			$success=1;
		}
		//echo $search_pos." ".$slen." ".htmlspecialchars($check)."<br/>";
		$search_pos-=1;
		$slen-=1;
		if($search_pos<0){break;}
	} 
	$length = $newString-$search_pos;
	$icon_name = substr($icon_name,$search_pos,$length);
	echo "<br/>extracted icon name from xml successfully";
	return htmlspecialchars($icon_name);
}

function pullPNG($myFile,$XML_end, $icon){
	$iconAvailable = file_exists($icon);
	$fh = fopen($myFile, 'rb');
	$filesize=filesize($myFile);
	fseek($fh, $XML_end);
	$data = fread( $fh, $filesize - $XML_end);
	for ($i=0; $i<($filesize-$XML_end)-3; $i++){
		if ($data[$i+0] == chr(0x89) && $data[$i+1] == chr(0x50) && $data[$i+2] == chr(0x4e) && $data[$i+3] == chr(0x47)){
			echo '<p>Found PNG</p>';
			$writeHandle = fopen($icon, "wb" );
			fwrite($writeHandle, substr($data,$i));
			fclose($writeHandle);
			$iconAvailable = true;
			break;
		}      
	}
	fclose($fh);
	echo '[img]' . $icon . '[/img]';
}

output

just need to be able to pull the screenshot/image now and we have what we need from an uploaded pnd file :)

thanks wizard,rock,urjman and anyone else who has been chipping in.
 
Last edited by a moderator:
Just for fun:

http://www.gamesdev.com/pandora/

That is generated from this code:

Code:
<html>
 <head>
  <title>Pandora App Store</title>
 </head>
 <body>

 <?php 
 
 define( "kReadBufferSize", 4096 );
 define( "kPxmlBegin",      "<PXML" );
 define( "kPxmlEnd",        "</PXML>" );
 define( "kPxmlOverlap",    max( strlen( kPxmlBegin ), strlen( kPxmlEnd ) ) );
 define( "kPngMagic",       chr( 0x89 ) . chr( 0x50 ) . chr( 0x4e ) . chr( 0x47 ) );
 
function getDirectoryList($directory) 
{

    // create an array to hold directory list
    $results = array();
    
    // create a handler for the directory
    $handler = opendir($directory);
    
    // open directory and walk through the filenames
    while ($file = readdir($handler)) 
    {    
        // if file isn't this directory or its parent, add it to the results
        if ( $file != "." && $file != ".." ) 
        {
            $results[] = $file;
        }    
    }
    
    // tidy up: close the handler
    closedir($handler);
    
    // done!
    return $results;
} 
 
function retreivePxmlDataAndIcon( $filename )
{
    // If this file has already been processed we have an early out!
    if ( file_exists( $filename . ".pxml.xml" ) )
    {
        $pxmlData   = $filename . ".pxml.xml";
        $iconData   = false;
        
        if ( file_exists( $filename . ".icon.png" ) )
        {
            $iconData = $filename . ".icon.png";
        }
        return array( $pxmlData, $iconData ); 
    }
    
    // Make sure file exists.
    if ( ! file_exists( $filename ) )
    {
        return false;
    }
    
    // Gets the file size.
    $fileSize = filesize( $filename );
    
    // If it is too short to even contain the begin/end PXML tags there is no hope.
    if ( $fileSize < strlen( kPxmlBegin ) + strlen( kPxmlEnd ) )
    {
        return false;
    } 
    
    $startOfPxmlData = false;
    $endOfPxmlData = false;

    // Open the file for binary read.
    $handle = fopen($filename, "rb");
    
    // Set the last read position to essentially be the end of the file.
    $lastReadPosition = $fileSize - kPxmlOverlap;
    
    // Until we have read all the way back to the start of the file.
    while ( $lastReadPosition != 0 )
    {
        // Go back one buffer size, but nudge forward by the overlap amount.
        $readPosition       = max( $lastReadPosition - kReadBufferSize + kPxmlOverlap, 0 );
    
        // Seek.
        fseek( $handle, $readPosition );
        
        // Read.
        $data               = fread( $handle, kReadBufferSize );
        
        // If we haven't found the end tag yet.
        if ( $endOfPxmlData === false )
        {
            // Look for end tag.
            $ix = stripos( $data, kPxmlEnd );
            
            // If found...
            if ( $ix !== false )
            {
                // Store position.
                $endOfPxmlData = $readPosition + $ix + strlen( kPxmlEnd );        
            }
        }
        
        // If we have foudn end tag, look for start tag.
        if ( $endOfPxmlData !== false )
        {
            $ix = stripos( $data, kPxmlBegin );
            if ( $ix !== false )
            {
                 $startOfPxmlData = $readPosition + $ix;   
                 
                 // Once we have found start and end tags we can break out of while loop.
                 break;     
            }   
        }
        
        // Save the last read position for next loop.
        $lastReadPosition   = $readPosition;
    } 
    
    // Default our pxmlData to false.
    $pxmlData = false; 
    $iconData = false;
    
    // If we found begin/end PXML tags then pull out PXML data.
    if ( $startOfPxmlData !== false && $endOfPxmlData !== false )
    {
        fseek( $handle, $startOfPxmlData );
        $data = fread( $handle, $endOfPxmlData - $startOfPxmlData );
        
        // Filename to write to.
        $pxmlData = $filename . ".pxml.xml";
        $writeHandle = fopen( $pxmlData, "wb" );
        fwrite( $writeHandle, $data );
        fclose( $writeHandle );
        
        // See if there is an icon after the data.
        fseek( $handle, $endOfPxmlData );
        
        // Read rest of file.
        $data = fread( $handle, $fileSize - $endOfPxmlData );
        
        // Search for PNG magic.
        $ix = stripos( $data, kPngMagic );
        
        if ( $ix !== false )
        {
            $iconData = $filename . ".icon.png";
            $writeHandle = fopen( $iconData, "wb" );
            fwrite( $writeHandle, substr( $data, $ix ) );
            fclose( $writeHandle );
        }
    }
    
    // Close file handle.
    fclose( $handle );
    
    // Return our results.
    return array( $pxmlData, $iconData );
}

function processPnd( $filename )
{
    list( $pxmlData, $iconData ) = retreivePxmlDataAndIcon( $filename );
    
    if ( $pxmlData !== false )
    {
        $xmlDoc = new DOMDocument( );
        $xmlDoc->load( $pxmlData );
        $pxml = $xmlDoc->getElementsByTagName("PXML"); 
        foreach ($pxml as $item)
        {
            $applications = $item->getElementsByTagName("application");
            foreach ($applications as $application)
            {
                $titles = $item->getElementsByTagName("title");
                foreach ( $titles as $title )
                {
                    echo '<p> *** ' . $title->getAttribute( "lang" ) . '=' . $title->nodeValue . ' *** </p>';
                }
                
                $descriptions = $item->getElementsByTagName("description");
                foreach ( $descriptions as $description )
                {
                    echo '<p> *** ' . $description->getAttribute( "lang" ) . '=' . $description->nodeValue . ' *** </p>';
                }
            }
        }    
        
        if ( $iconData !== false )
        {
            echo '[img]' . $iconData . '[/img]';
        }
    }    
}

$dirList = getDirectoryList( "." );

foreach ( $dirList as $filename )
{
    if ( stripos( pathinfo( $filename, PATHINFO_EXTENSION ), "pnd" ) !== false )
    {
        processPnd( $filename );
    }
}

?> 
 </body>
</html>

Goes through all the .PND's uploaded to the server and displays a bit of information about them - I only have a couple of PND's on there for testing purposes! It uses a Php Xml built-in module to parse the actual XML data, at the moment it just displays a couple of fields, but you can see from the code it'd be trivial to write out whatever text you like. Anyone with decent web design skills (don't look here) should be able to create a nice little Apple style AppStore, with pretty rounded pictures with gleams on them, for each applications, with download links, information boxes, etc. etc. and shouldn't require any more than having the PND's in the directory.

I think the next nice extension would be to store the data in a MySQL database, rather than plain text files - I may or may not take a look at that (I don't really have any end goal with this, I just fancied learning a bit of PHP).

Steve
 
Last edited by a moderator:
cool :) rockthesmuft thats what im working on, I must say you are on fire, I had just picked up from where we left off and was creating an xml parser for our PXML so I could save it to my mysql database I have been building for this project :)

however I think the wiki pxml standard page needs updating because all the xml I have stripped from pnd's on my testing server has <application id="" appdata=""> in the xml which is not on the standards page.

as said before all I need is away to pull the pull the preview pics/screenshots from the binary I dont suppose you figured that one out? :)

if you fancy working together on this project I can set you up a ftp account to my server so we can work on it together :p let me know.
 
milkshake said:
however I think the wiki pxml standard page needs updating because all the xml I have stripped from pnd's on my testing server has <application id="" appdata=""> in the xml which is not on the standards page.
Right at the top of that page, it says "NOTE: This version of the PXML format is out of date. Please see the new, updated, PXML specification." The proper page seems to be up-to-date (that's the spec to which I've been working). Also, there's a PXML template that has useful notes on the spec.
 
Last edited by a moderator:
Ok I have wrote a quick upload test form for people to test the upload of a pnd file.
it takes the pnd extracts the xml takes needed info from xml and adds the data to an array then populates a form with the details of the pnd file.

test site

please test it and let me know if it works fine for you.

p.s. it doesn,t store anything to a database yet and it deletes the pnd file after its took the info from it for now as its for testing purposes.

let me know if any .pnd files do not work.

big thanks for EVERYONES!! help so far.
 
Anyone have any thoughts about the best way to store various translations of strings in a database? One option is to add a field for every variation that we come across, e.g. start off with:

  • title_en-US
  • description_en-US

But then if a pnd file is examined that contains some French Canadian text, it'd add new fields to the database:

  • title_fr-CA
  • description_fr-CA

Obviously, it'd only add new fields if they didn't already exist. Then when it comes to displaying the website, we'd know what language we want to display in, say it was French Canadian, it'd initially look for fields with _fr-CA after them, if they aren't found we'd look for _fr-FR, if they aren't found we'd just look for the default _en-US.

Is there a better way?

Steve
 
they way im going to do it is to have a table set out like this

Code:
|-------|--------|--------|-----------|
|appid  |locale  |title   |description|
|-------|--------|--------|-----------|
|dh76d  |en_US   |Vice x64|A c64 Emu..|
|-------|--------|--------|-----------|
|dh76d  |fr_FR   |Vice x64|Un c64 Emu.|
|-------|--------|--------|-----------|

Save each locale with the same appid so you have mutiple results per id, then you can just bring up all different locales (title/description) via app id when needed - does that make sense?
If you have a different lanugage description but not diff language title you can just use the same title.

and of course the list of different locales would grow as each pnd is added.

instead of working seprately why dont we work together? it seems silly to make 2 seperate sites?

heres what im using to generate my associative array.
Code:
function xml2AssoArray($xmlFile){
		
	$appLength = 1;
        $xmlDoc = new DOMDocument( );
        $xmlDoc->load($xmlFile);
		
	//create associative array to loop through to collect needed data
	$xmlElements['application'] = array('id'=>0,'appdata'=>0);
	$xmlElements['title'] = array('lang'=>0,'title'=>1);
	$xmlElements['description'] = array('lang'=>0,'description'=>1);
	$xmlElements['author'] = array('name'=>0,'website'=>0);
	$xmlElements['version'] = array('major'=>0,'minor'=>0,'release'=>0,'build'=>0);
	$xmlElements['category'] = array('name'=>0);
	$xmlElements['subcategory'] = array('name'=>0);
		
	foreach($xmlElements as $xmlElementName=>$attribCheck){

		//loop through xml element tags
        	$searchXml = $xmlDoc->getElementsByTagName($xmlElementName);
		$length = $searchXml->length;
			
		//we only want to check the first app in the pnd or things will get extremely complicated so
		//if there is more than one application tag in the pxml set $applength to 2
		if($length > 1 && $xmlElementName == 'application'){
			$appLength = 2;
		}
			
		//foreach searchnode...
		for ($i = 0; $i < $length; $i++) {

			//loop through attrib name and get bool values from our earlier created array
			foreach($attribCheck as $xmlAttrib=>$bool){
					
				//if bool is value 0 then ask for attribute value if not ask for element value
				//then we place the info into an associative which is very usefull
				if($bool!=1){

					$application[$xmlAttrib] = $searchXml->item(0)->getAttribute($xmlAttrib);
						
					//if there are more xml tags of the same name and there is only one application 
					//in the pnd we can safely add multiple tag values to array
					if($i>0 && $appLength < 2){
						$application[$xmlAttrib.$i] = $searchXml->item($i)->getAttribute($xmlAttrib);
					}
				}else{
						
					$application[$xmlAttrib] = $searchXml->item(0)->nodeValue;
						
					//if there are more xml tags of the same name and there is only one application 
					//in the pnd we can safely add multiple tag values to array
					if($i>0 && $appLength < 2){
						$application[$xmlAttrib.$i] = $searchXml->item($i)->nodeValue;
					}
				}
				
			}
		$details[$xmlElementName] = $application;
		unset($application);
		}
	}
	unlink($xmlFile);//delete xml file as no longer needed
	return $details;//return multidimensional associative array packed with friendly data :)
}

output e.g. vicex64 would be...

Code:
Array
(
    [application] => Array
        (
            [id] => vicex64.pickle
            [appdata] => 
        )

    [title] => Array
        (
            [lang] => en_US
            [title] => Vice x64
        )

    [description] => Array
        (
            [lang] => en_US
            [lang1] => de_DE
            [description] => A C64 Emulator.
            [description1] => Ein C64-Emulator
        )

    [author] => Array
        (
            [name] => Ported by Pickle
            [website] => http://www.viceteam.org/
        )

    [version] => Array
        (
            [major] => 2
            [minor] => 2
            [release] => 0
            [build] => 0
        )

    [category] => Array
        (
            [name] => Game
        )

    [subcategory] => Array
        (
            [name] => Emulator
        )

)
 
milkshake said:
instead of working seprately why dont we work together? it seems silly to make 2 seperate sites?

Oh, don't worry, I wasn't working on a site, just wanted to learn a bit of php + mysql! I'm 100% happy to share anything I do with you (and anyone else though) so it can be incorporated into your site!

Steve
 
Last edited by a moderator:
Cool cool, well I'm glad of any help i can get when i get stuck, did my advice about storing locales ina db help you make up ur mind on how you would proceed?
 
If you're still working on icon, just remember the trick -- if theres anything after the end of the PXML ("PXML>"), then its an icon; just grab all of it and save it out, or accrue it to RAM, whatever. Its really a piece of cake :)

The screenshots are the hard one (and really make me wish we'd used iso+zip so that we could put meta stuff in an indexed compressed form at the end, and everything can do zip...); thats for version 2 though :)

jeff
 
I have the icon rip working now as you can see further up but obviously because the screenshots arnt appended to the pnd there isnt really any way for me to pull them without my isp having an upto date squashfs-tools installed which mine doesnt otherwise this would be a sinch :)
 
milkshake said:
did my advice about storing locales ina db help you make up ur mind on how you would proceed?

Yes it did help somewhat, although I still haven't decided how I'd prefer to proceed. There are various options:

My initial suggestion:

Code:
------------------------------------------------------------------------------------------
| AppID    | Title-en_US | Title-fr-CA | Description-en_US | Description-fr-CA | Version |
------------------------------------------------------------------------------------------
  monopoly   Monopoly US   Monopolie     Stars and stripes   Oui oui             1.0.0.2

Your (improved) suggestion:

Code:
------------------------------------------------------------------
| AppID     | Locale | Title       | Description       | Version |
------------------------------------------------------------------
  monopoly    en-US    Monopoly US   Stars and stripes   1.0.0.2
  monopoly    fr-CA    Monopolie     Oui oui             1.0.0.2

Potentially a further improvement (maybe not?):

First part contains unique key (AppID) and any other non translatable data:

Code:
----------------------
| AppID    | Version |
----------------------
  monopoly   1.0.0.2

Then per translation:

Code:
en-US
----------------------------------------------
| AppID    | Title       | Description       |
----------------------------------------------
  monopoly   Monopoly US   Stars and stripes

fr-CA
----------------------------------------------
| AppID    | Title       | Description       |
----------------------------------------------
  monopoly   Monopolie     Oui oui

I think I need to read a book on MySQL/database design though, as I'm just lashing out in the dark at the moment! I've never done anything with databases before so it is all new territory for me! I just have this feeling it'd be nice not to repeat the same details (e.g. version number, category, sub-category, author) in every translation.

Steve
 
Last edited by a moderator:
you wouldnt need to repeat the same details (e.g. version number, category, sub-category, author) in every translation as you would have those in a separate table and the appid would link them so when you call them out of the database its easy to get the info you need because your primary key which will probably be your appid links all the information you need.
 
You can find the latest version of my php script below:

Code:
<html>
 <head>
  <title>Pandora App Store</title>
 </head>
 <body>

 <?php

// Setup various directory locations.
define( "kRootDir",			"C:/xampp/xampp/htdocs/pandora" );
define( "kUploadDir",		kRootDir . "/upload" );
define( "kDownloadDir",		kRootDir . "/download" );
define( "kIconDir",			kRootDir . "/icon" );

// Read buffer size.
define( "kReadBufferSize",	4096 );

// Tags to look for when identifying pxml data.
define( "kPxmlBegin",		"<PXML" );
define( "kPxmlEnd",			"</PXML>" );

// Amount of overlap to read when identifying pxml tags.
define( "kPxmlOverlap",		max( strlen( kPxmlBegin ), strlen( kPxmlEnd ) ) );

// Png magic.
define( "kPngMagic",		chr( 0x89 ) . chr( 0x50 ) . chr( 0x4e ) . chr( 0x47 ) );

// Utility function to join two paths.
function JoinPaths( )
{
    $args	= func_get_args( );
    $paths	= array( );
    foreach ( $args as $arg )
	{
        $paths = array_merge( $paths, ( array )$arg );
    }
    foreach ( $paths as &$path )
	{
        $path = trim( $path, '/' );
    }
    return join( '/', $paths );
}

// Utility function to get a directory listing.
function GetDirectoryList($directory)
{

    // create an array to hold directory list
    $results = array();

    // create a handler for the directory
    $handler = opendir($directory);

    if ( $handler === false )
    {
        return $results;
    }

    // open directory and walk through the filenames
    while ($file = readdir($handler))
    {
        // if file isn't this directory or its parent, add it to the results
        if ( $file != "." && $file != ".." )
        {
            $results[] = $file;
        }
    }

    // tidy up: close the handler
    closedir($handler);

    // done!
    return $results;
}

function RetreivePxmlDataAndIcon( $filename )
{
    // Make sure file exists.
    if ( ! file_exists( $filename ) )
    {
        return array( false, false );
    }

    // Gets the file size.
    $fileSize = filesize( $filename );

    // If it is too short to even contain the begin/end PXML tags there is no hope.
    if ( $fileSize < strlen( kPxmlBegin ) + strlen( kPxmlEnd ) )
    {
        return false;
    }

    $startOfPxmlData = false;
    $endOfPxmlData = false;

    // Open the file for binary read.
    $handle = fopen($filename, "rb");

    // Set the last read position to essentially be the end of the file.
    $lastReadPosition = $fileSize - kPxmlOverlap;

    // Until we have read all the way back to the start of the file.
    while ( $lastReadPosition != 0 )
    {
        // Go back one buffer size, but nudge forward by the overlap amount.
        $readPosition       = max( $lastReadPosition - kReadBufferSize + kPxmlOverlap, 0 );

        // Seek.
        fseek( $handle, $readPosition );

        // Read.
        $data               = fread( $handle, kReadBufferSize );

        // If we haven't found the end tag yet.
        if ( $endOfPxmlData === false )
        {
            // Look for end tag.
            $ix = stripos( $data, kPxmlEnd );

            // If found...
            if ( $ix !== false )
            {
                // Store position.
                $endOfPxmlData = $readPosition + $ix + strlen( kPxmlEnd );
            }
        }

        // If we have foudn end tag, look for start tag.
        if ( $endOfPxmlData !== false )
        {
            $ix = stripos( $data, kPxmlBegin );
            if ( $ix !== false )
            {
                 $startOfPxmlData = $readPosition + $ix;

                 // Once we have found start and end tags we can break out of while loop.
                 break;
            }
        }

        // Save the last read position for next loop.
        $lastReadPosition   = $readPosition;
    }

    // Default our pxmlData to false.
    $pxmlData = false;
    $iconData = false;

    // If we found begin/end PXML tags then pull out PXML data.
    if ( $startOfPxmlData !== false && $endOfPxmlData !== false )
    {
    	// Read pxml data.
        fseek( $handle, $startOfPxmlData );
        $pxmlData = fread( $handle, $endOfPxmlData - $startOfPxmlData );

        // See if there is an icon after the data.
        fseek( $handle, $endOfPxmlData );

        // Read rest of file.
        $data = fread( $handle, $fileSize - $endOfPxmlData );

        // Search for PNG magic.
        $ix = stripos( $data, kPngMagic );

        if ( $ix !== false )
        {
            $iconData = substr( $data, $ix );
        }
    }

    // Close file handle.
    fclose( $handle );

    // Return our results.
    return array( $pxmlData, $iconData );
}

// ---------------------------------------------------------------------------------------------------------------------

class Translation
{
	public	$Title;
	public	$Description;

    public function __construct( $title, $description )
    {
        $this->Title		= $title;
        $this->Description	= $description;
    }
}

// ---------------------------------------------------------------------------------------------------------------------

class PndInfo
{
    public	$AppId;
    public	$Version;
    public	$DownloadName;
    public	$Translations;

    public function __construct( $appId, $version, $downloadName, $translations )
    {
        $this->AppId    	= $appId;
        $this->Version  	= $version;
        $this->DownloadName	= $downloadName;
        $this->Translations	= $translations;
    }

    private function GetTranslation( $array, $region = none )
    {
    	// If there are no items, return nothing.
    	if ( sizeof( $array ) === 0 )
    	{
    		return none;
    	}

    	// If no region is specified...
    	if ( $region === none )
    	{
    		if ( array_key_exists( "en_US", $array ) )
    		{
    			// Try en_US first.
    			return $array[ "en_US" ];
    		}
    		else if ( array_key_exists( "en_GB", $array ) )
    		{
    			// Next try en_GB.
    			return $array[ "en_GB" ];
    		}
    		else
    		{
    			// Return whatever we find first.
    			foreach ( $array as $item )
    			{
    				return $item;
    			}
    		}
    	}

    	// A region has been specified, so find exact match.
		foreach ( $array as $key => $value )
		{
			if ( $key === $region )
			{
				return $value;
			}
		}

		// Didn't find specified region.
		return none;
    }

    public function GetTitle( $region = none )
    {
		return $this->GetTranslation( $this->Translations, $region )->Title;
    }

    public function GetDescription( $region = none )
    {
		return $this->GetTranslation( $this->Translations, $region )->Description;
    }
}

// ---------------------------------------------------------------------------------------------------------------------

class Database
{
    private $m_Host;
    private $m_Username;
    private $m_Password;
    private $m_Database;
    private $m_MainTable;
    private $m_TranslationTable;

	// Connect to database on construction.
    public function __construct( )
    {
        $this->m_Host				= localhost;
        $this->m_Username			= "root";
        $this->m_Password			= "";
        $this->m_Database			= "pandora-pnd";
        $this->m_MainTable			= "pnd-info";
        $this->m_TranslationTable	= "pnd-translation";

        mysql_connect( $this->m_Host, $this->m_Username, $this->m_Password ) or die ( "Failed to connect to database" );
    }

	// Close connection.
    function __destruct( )
    {
        mysql_close( );
    }

    private function Sql[quote=" $text "]
    {
    	return '`' . $text . '`';
    }[/quote]	// Query current entries in database.
    function Query( $appId = none )
    {
        $returnValues = array( );

        mysql_select_db( $this->m_Database ) or die( "Unable to select database" );
        $query      = 'SELECT * ';
		$query     .= 'FROM ' . $this->Sql[quote=" $this->m_MainTable "]		 . ' ';
		$query     .= 'JOIN ' . $this->Sql[quote=" $this->m_TranslationTable "] . ' ON ' . $this->Sql[quote=" $this->m_MainTable "] . '.appid = ' . $this->Sql[quote=" $this->m_TranslationTable "] . '.appid';[/quote]		// If we have been given an AppId to search for, then using this as the WHERE clause.
        if ( $appId !== none )
        {
            $query .= " WHERE `" . $this->m_MainTable . "`.appid = '" . $appId . "'";
        }

        $result     = mysql_query( $query );
        $numRows    = mysql_numrows( $result );

		// Loop through results.
        for ( $i = 0; $i < $numRows; $i += 1 )
        {
        	// Pull out result data.
        	$currentAppId			= mysql_result( $result, $i, "appid" );
        	$currentVersion			= mysql_result( $result, $i, "version" );
        	$currentDownloadName	= mysql_result( $result, $i, "download_name" );
        	$currentLocale			= mysql_result( $result, $i, "locale" );
        	$currentTitle			= mysql_result( $result, $i, "title" );
        	$currentDescription		= mysql_result( $result, $i, "description" );

			// Look for existing result (in our return array) for this application ID.
        	$foundExisingResult = false;
        	foreach ( $returnValues as $returnValue )
        	{
        		if ( $returnValue->AppId == $currentAppId )
        		{
        			// If we found one, then just add the current locale.
        			$returnValue->Translations[ $currentLocale ] = ( new Translation( $currentTitle, $currentDescription ) );

        			// Set found flag.
        			$foundExisingResult = true;

        			// Break out of loop.
        			break;
        		}
        	}

        	// If we did not find existing result, then create one now.
        	if ( $foundExisingResult === false )
        	{
	            $returnValues[] = new PndInfo(
	                $appId			= $currentAppId,
	                $version		= $currentVersion,
	                $downloadName	= $currentDownloadName,
	                $translations	= array(
						$currentLocale => new Translation( $currentTitle, $currentDescription )
					)
	            );
        	}
        }

        return $returnValues;
    }

	// Add a new entry if primary key, AppId, doesn't already exist in database.
    function Add( $pndInfo )
    {
        // Make sure this entry doesn't already exist.
        if ( sizeof( $this->Query( $pndInfo->AppId ) ) )
        {
            return false;
        }

        // Select main database.
        mysql_select_db( $this->m_Database ) or die( "Unable to select database" );

        // Add main info.
        $query =    'INSERT INTO ' . $this->Sql[quote=" $this->m_Database "] . '.' . $this->Sql[quote=" $this->m_MainTable "] .
                    " (`appid`, `version`, `download_name`) VALUES (" .
                    "'" . $pndInfo->AppId			. "', " .
                    "'" . $pndInfo->Version			. "', " .
                    "'" . $pndInfo->DownloadName	. "'" .
                    ")";[/quote]        mysql_query( $query ) or die ( "Failed to insert into database" );

		// Add translations.
		foreach ( $pndInfo->Translations as $region => $translation )
		{
	        $query =    'INSERT INTO ' . $this->Sql[quote=" $this->m_Database "] . '.' . $this->Sql[quote=" $this->m_TranslationTable "] .
	                    " (`appid`, `locale`, `title`, `description`) VALUES (" .
	                    "'" . $pndInfo->AppId			. "', " .
	                    "'" . $region					. "', " .
	                    "'" . $translation->Title		. "', " .
	                    "'" . $translation->Description	. "'" .
	                    ")";[/quote]        	mysql_query( $query ) or die ( "Failed to insert into database" );
		}
    }
}

// ---------------------------------------------------------------------------------------------------------------------

// Create connection to database.
$db = new Database( );

// Get list of all files to be processed.
$dirList = GetDirectoryList( kUploadDir );

// Go through each file.
foreach ( $dirList as $filename )
{
	// If it is a .pnd then process.
    if ( stripos( pathinfo( $filename, PATHINFO_EXTENSION ), "pnd" ) !== false )
    {
    	// Get pxml data and icon.
        list( $pxmlData, $iconData ) = RetreivePxmlDataAndIcon( kUploadDir . $filename );

		// If data is valid...
	    if ( $pxmlData !== false )
	    {
	    	// Process xml.
	        $xmlDoc = new DOMDocument( );
	        $xmlDoc->loadXML( $pxmlData );
	        $pxml = $xmlDoc->getElementsByTagName("PXML");
	        foreach ($pxml as $item)
	        {
	            $applications = $item->getElementsByTagName("application");
	            foreach ($applications as $application)
	            {
	            	// AppId.
	                $AppId = $application->getAttribute( "id" );

					// Version.
					$Version = none;
	                $versions = $item->getElementsByTagName( "version" );
	                foreach ( $versions as $version )
	                {
	                    $major      = $version->getAttribute( "major" );
	                    $minor      = $version->getAttribute( "minor" );
	                    $release    = $version->getAttribute( "release" );
	                    $build      = $version->getAttribute( "build" );

	                    $Version = "$major.$minor.$release.$build";
	                }

	                // Translation(s).
	                $Translations = array( );

	                $titles = $item->getElementsByTagName("title");
	                foreach ( $titles as $title )
	                {
	                	$locale = $title->getAttribute( "lang" );
	                	$Translations[ $locale ] = new Translation( $title->nodeValue, none );
	                }

	                $descriptions = $item->getElementsByTagName("description");
	                foreach ( $descriptions as $description )
	                {
	                	$locale = $description->getAttribute( "lang" );
	                	$title	= none;
	                	if ( array_key_exists( $description->getAttribute( "lang" ), $Translations ) )
	                	{
	                		$title = $Translations[ $locale ]->Title;
	                	}
                		$Translations[ $locale ] = new Translation( $title, $description->nodeValue );
	                }

					// Add to database.
	                $db->Add(
						new PndInfo(
							$AppId,
							$Version,
							basename( $filename ),
							$Translations
						)
					);
	            }

				// Create download folder of AppId.
				$appDownloadDir = kDownloadDir . "/" . $AppId;
				if ( ! file_exists( $appDownloadDir ) )
				{
					mkdir( $appDownloadDir );
				}
				rename( kUploadDir . "/" . $filename, $appDownloadDir . "/" . $filename );
	        }
	        if ( $iconData !== false )
	        {
	        	$fh = fopen( kIconDir . "/" . $AppId . ".png", "wb" );
	        	fwrite( $fh, $iconData );
	        	fclose( $fh );
	        }
	    }
    }
}

// ---------------------------------------------------------------------------------------------------------------------
// Dump out contents of database in default translation just to test!

$results = $db->Query( );
foreach ( $results as $result )
{
    echo '<p>AppId : ' . $result->AppId . ' - Version : ' . $result->Version . ' - Title : ' . $result->GetTitle( ) . ' - Description : ' . $result->GetDescription( ) . '</p>';
    $pngFilename = kIconDir . "/" . $result->AppId . ".png";
    if ( file_exists( $pngFilename ) )
    {
    	echo '[img]' . $pngFilename . '[/img]';
    }
    echo '<p><a href="' . kDownloadDir . '/' . $result->AppId . '/' . $result->DownloadName . '">download</a></p>';
}

?>
 </body>
</html>

Currently it finds all the .pnd files in an 'upload' directory, and then populates a MySQL database based on their contents, and then moves them into a downloads directory.

Once it has processed all the waiting 'upload' files, it'll dump out all the information currently in the database and display each entry with an icon (where appropriate) and a download link.

I actually opted to the database as you suggested (after trying my 'hybrid' version first, and decided it wasn't so go after all).

I was thinking, I am tempted to write some php that will go through the 'official' app store and pull all the .pnd files out of it, that way they could be pulled into my test site, or more importantly your real site. You haven't already done anything like this have you Milkshake?

Steve
 
Last edited by a moderator:
Rockthesmurf said:
I was thinking, I am tempted to write some php that will go through the 'official' app store and pull all the .pnd files out of it, that way they could be pulled into my test site, or more importantly your real site. You haven't already done anything like this have you Milkshake?
That would be really nice. If it could be extended to do the same for openhandhelds.org, then your site would pretty much be the definitive source for Pandora applications, especially if you have the repo spec implemented (and maybe Esn wouldn't have to keep updating the wiki app lists).

Speaking of the repo spec, I thought I should let you know that I've started a little work on a client-side app to interface with it. Right now, I'm (slowly) working on backend stuff, then I'll create a command-line interface, then I'll create a GUI. I'll make a post about it when it's somewhat presentable.
 
Last edited by a moderator:
Back
Top