Upload and download level files from GLBasic to server

Previous topic - Next topic

BdR

I'm working on a puzzle game for the iphone using GLBasic and it will have a level editor so players can make their to own puzzles. A level is simply a grid with items, similar to Sokoban levels (but my game is not Sokoban ;))

But I also want to be able to send these user created levels to a website or server. Is it possible in GLBasic to send files to a server? What would be the best way to do this. I guess you could use PHP scripts, similar to the php script to send and recieve online-highscores.. But I don't know that much about it, how would you send an entire file using php? :doubt:

On the news page it says that Wumbo's Adventure will have an online level library so I guess it is possible. Will the next version of GLBasic have added features or commands to do these kind of things?

Moru

I have some examples on my homepage on how to send a file to an apache webserver with PHP and MySQL. Fully running examples with source for both client and server.

Kitty Hello

OK, here's how I do it:

Code (glbasic) Select

FUNCTION UploadLevel%: nLevel%
LOCAL out$, ok%
LOCAL file$ = file to upload
[...]
ok% = WumboStore("action=host&review", out$, file$)

ENDFUNCTION



FUNCTION GetAllLevels:
GETFILE file$+"/updateinfo", 0, my_version%

DEBUG "Local version: "+my_version%+"\n"

// get level list and update version
LOCAL levels$
IF WumboStore("action=list"+review$, levels$)=FALSE
RETURN FALSE
ENDIF

LOCAL remoteversion%=INTEGER(levels$)
IF my_version% >= remoteversion%
// ConsolePrt(lang$("Levels are up to date", "Level sind auf aktuellem Stand"))
RETURN TRUE
ENDIF

// finally download the levels
LOCAL lv$[]
SPLITSTR(levels$, lv$[], "\n")
DIMDEL lv$[], 0
LOCAL i%=0
FOREACH lev$ IN lv$[]
INC i, 1
LOCAL prtlev$
prtlev$ = lev$
IF LEN(prtlev$)>30 THEN prtlev$="..." + MID$(prtlev$, LEN(prtlev$)-30)
IF WumboStore("action=send&level="+lev$+review$, levels$)
LOCAL file% = GENFILE()
IF OPENFILE(file%, file$+"/"+lev$, 0)
WRITESTR file%, levels$
CLOSEFILE file%
ENDIF
ELSE
RETURN FALSE
ENDIF
NEXT

// save the updateinfo version to the local drive
IF OPENFILE(0, GetEditLevelDir$()+"/updateinfo", 0)
WRITELINE 0, remoteversion%
CLOSEFILE 0
ENDIF
ENDFUNCTION

FUNCTION WumboStore%: get$, BYREF out$, levelfile_upload$=""
out$="error"

LOCAL boundary$, postdata$
// make a boundary string. That's a few "--" and some random characters
boundary$ = "----------------bOunDarY-------"+INTEGER(GETTIMERALL()*1000.0)+"-"+RND(99999)+"--"
IF LEN(levelfile_upload$)
INC postdata$, AddHttpPostFile$("level", levelfile_upload$, boundary$, "text/plain")
ENDIF

LOCAL server$="www.glbasic.com" // the server URL
LOCAL curl$  ="/wumbo/wumbolev.php?" + get$ // the path on the server (start with / )
LOCAL proxy$ =""  // leave this empty for a direct connection
LOCAL port%=80                // use port 80 for direct connection
LOCAL ret$

out$ = HttpPost$(proxy$, server$, curl$, port%, boundary$, postdata$)

DEBUG out$ + "\n\n"

IF INSTR(out$, "$$error")>=0 OR LEN(out$)=0 THEN RETURN FALSE
RETURN TRUE
ENDFUNCTION





// ---------------------------------------------------------------------
// Send a HTTP POST Request and return what the webbrowser would display
// ---------------------------------------------------------------------
FUNCTION HttpPost$: proxy$, server$, curl$, port%, boundary$, postdata$
LOCAL header$ // the HTTP post header

// Fix proxy urls
IF LEN(proxy$)
curl$ = "http://"+server$+curl$
server$ = proxy$
ENDIF

// end post data
postdata$ = postdata$ + "--"+boundary$+"--"

LOCAL send$
send$ = "POST "+curl$+" HTTP/1.0\r\n"
INC send$, "User-Agent: GLBasic\r\n"
INC send$, "Host:"+server$+"\r\n"
INC send$, "Accept: */*\r\n"
INC send$, "Content-Type: multipart/form-data, boundary="+boundary$+"\r\n"
INC send$, "Content-Length: "+LEN(postdata$) + "\r\n"
INC send$, "\r\n"
INC send$, postdata$

LOCAL sock%, file$
SOCK_INIT()
sock% = SOCK_TCPCONNECT(server$, port%)
IF sock%<0
ConsolePrt(lang$("Connection failed - Wifi active?", "Verbindung fehlgeschlagen. Wifi an?"))
STDERR "Can't connect to "+server$+"\n"
STDERR NETGETLASTERROR$()+"\n"
RETURN ""
ENDIF
IF SOCK_TCPSEND(sock%, send$) > 0
LOCAL chunk$, rv%
WHILE TRUE
chunk$=""
rv% = SOCK_RECV(sock%, chunk$, 1024)
SELECT rv%
CASE -1
STDERR NETGETLASTERROR$()+"\n"
file$=""
BREAK
CASE -2 // non blocking socket not would block
SLEEP 5
CASE 0
BREAK // ok, reading is done
CASE >0
INC file$, chunk$
ENDSELECT
WEND
// jippieh! We got a response

// split HTTP header and actual file information apart
LOCAL pos%
pos = INSTR(file$, "\r\n\r\n")
IF pos>=0
header$ = MID$(file$, 0, pos%)
file$   = MID$(file$, pos%+4)
ELSE
header$=""
ENDIF
ELSE
STDERR "can't open socket to "+server$+"\n"
ENDIF
SOCK_CLOSE(sock%)

RETURN file$
ENDFUNCTION




// ---------------------------------------------------------------------
// add a variable to a HTTP POST query
// ---------------------------------------------------------------------
FUNCTION AddHttpPostVar$: varname$, value$, boundary$
LOCAL postdata$
postdata$ = "--"+boundary$+"\r\nContent-Disposition: form-data; name=\""+varname$+"\"\r\n\r\n"+value$+"\r\n"
RETURN postdata$
ENDFUNCTION

// ---------------------------------------------------------------------
// upload a png image variable to a HTTP POST query
// ---------------------------------------------------------------------
FUNCTION AddHttpPostFile$: varname$, filepath$, boundary$, content_type$
LOCAL postdata$
LOCAL content$

IF OPENFILE(1, filepath$, TRUE)
LOCAL b%
WHILE ENDOFFILE(1)=FALSE
READBYTE 1, b%
// chr$(0) is a string of length "1", that contains a '\0' character!
content$ = content$ + CHR$(b%)
WEND
CLOSEFILE 1

postdata$ = "--"+boundary$+"\r\n"
INC postdata$, "Content-Disposition: form-data; name=\""+varname$+"\"; filename=\""+filepath$+"\"\r\n"
INC postdata$, "Content-Transfer-Encoding: binary\r\n"
IF LEN(content_type$)
INC postdata$, "Content-Type: "+content_type$+"\r\n"
ENDIF
INC postdata$, "Content-Type: application/octet-stream\r\n\r\n" + content$ + "\r\n";
ENDIF

RETURN postdata$
ENDFUNCTION



and the php code (part of)

Code (glbasic) Select

<?php
$DOCUMENT_ROOT $_SERVER['DOCUMENT_ROOT'];
if (substr ($DOCUMENT_ROOT, -1) == '/') { $DOCUMENT_ROOT substr ($DOCUMENT_ROOT0, -1); }

// global paths
$http_root="http://www.glbasic.com/wumbo/"// with trailing slash
    
$doc_root ="$DOCUMENT_ROOT/wumbo/"// with slash
    
// ?action=host        - http_post:level=file
// ?action=list        - list release levels
// ?action=list&review - list review levels
// ?action=hokay&level=levelname - put to release version
// ?action=send&level=levelname  - send to client [&review] possible
// ?action=send&level=levelname  - send to client

    
function list_files($directory)
    {
        
$handle1 = @opendir($directory);
        if (
$handle1)
        {
            while (
false !== ($file readdir($handle1)))
            {
                if (
$file!='.' && $file!='..' && $file!='updateinfo' && !is_dir($name) )
                {
                    echo ( 
$file "\n" );
                }
            }
            
closedir($handle1);
        }
    }
 
 
    
// Set release / inreview directory
    
$dir $doc_root;
    if(isset(
$_GET['review']) )
        
$dir .= 'inreview';
    else
        
$dir .= 'release';
    
    
// SEND level to client
    
if($_GET['action'] == 'send')
    {
        exit( 
file_get_contents$dir .'/'$_GET['level'] ) );
    }

    
// HOST LEVEL - inreview directory
    
if($_GET['action'] == 'host')
    {
        
// check file size limit (my biggest level is 31k)
        
if($_FILES['level']['size'] > 55000)
            exit(
'$$error - lev too big');
    
$file_lower strtolower($_FILES['level']['name']);
$accepted_endings=array(  '.lev''.pkg' );
        
if(in_array(substr($file_lower, -44), $accepted_endings))
{
$tmpfile $_FILES['level']['tmp_name'];
if(move_uploaded_file($_FILES['level']['tmp_name'], $doc_root 'inreview/' $_FILES['level']['name']))
{
                    exit(
"ok");
                }
                
// not, yet
                // exit("ok");
}
else
{
    echo("$$error - can't move uploaded file:\n".$doc_root 'inreview/' $_FILES['level']['name']."\n");
}
}
else
{
    echo "$$error - format not acceted\n";
}
    }
    
    
// LIST all files
    
if($_GET['action'] == 'list')
    {
        echo (int)
file_get_contents$dir .'/updateinfo' ) . "\n";

        
list_files($dir);
        exit(
''); // list ok
    
}
    
    
// Copy a file to release directory, increase the info number
    
if($_GET['action'] == 'hokay')
    {
        if( 
rename($doc_root.'inreview/'.$_GET['level'], $doc_root.'release/'.$_GET['level']) )
        {
            
// also move the package, if changed - ignore warnings
            
$package substr($_GET['level'], 0strlen($_GET['level'])-4) . '.pkg';
            
rename($doc_root.'inreview/'.$package$doc_root.'release/'.$package);

            
$version = (int)file_get_contents$doc_root.'release/updateinfo' ) + 1;
            
            echo 
'release version: '.$version."\n";

            
// update theupdate info number in the release version
            
settype($version'string');
            
$file fopen($$doc_root .'release/updateinfo''w');
            
fwrite$file$version);
            
fclose($file);
            exit(
'release - ok');
        }
        exit(
'$$error - unable to release');
    }  

    exit(
'$$error - unknown');
?>



BdR

Wow, thanks for the examples, this is really helpful. I really like the idea of just using URLs and then base64 string parameters to send the binary data. For now, I'll finish the core of the game so I can decide on the level-file-layout, then I'll start on the php and server side of things. :good: Thanks again.

BdR

Okay, it's been a while but recently I've found the time to continue on my project. I'm working on the server side of things and it is going great! :) I can't believe how easy and straight forward it all is. The php scripting is pretty easy too.

Here's how I want to do it:
I do a http post from GLBasic using the code example above, then the php script on the server handles the file and stores it. Also some sender information is sent, using PLATFORMINFO$("LOCALE") and PLATFORMINFO$("ID") etc. and this is also stored. The GLBasic program can request a list of available files, and then downloads each of these files.

:good: This all works now, but I do have 2 questions..

First of all how do you handle a re-upload of the same level or when a user sends an update of his levelfile? I think if I keep the md5-checksum and the PLATFORMINFO$("ID") to identify the sender, and then check for previously uploaded files using these 2 pieces of information will be enough. But any thoughts on this?

Second, how do you handle files with the same filename? For example, first a user sends a file called "mylevel.dat" and then another user also sends his "mylevel.dat". How do you distribute these two files to all other users? As "mylevel.dat" and "mylevel(1).dat"?

BdR

oh, and also.. This is probably a n00b question :doubt: but in the httppost example of Hello Kitty

Code (glbasic) Select
   // make a boundary string. That's a few "--" and some random characters
   boundary$ = "----------------bOunDarY-------"+INTEGER(GETTIMERALL()*1000.0)+"-"+RND(99999)+"--"


Why do you generate a random boundary for the HTTP-Post message? Why not just use a standard boundary that is the same everytime you post?

Moru

To store files with identical names you add the ID number from the database to the filename so you will always have a unique name. In the database you store the original name and the new so you can keep track of which file needs replacing when reuploading a file.

When users download a file they get the new unique name so it won't get replaced on downloading two files that was named the same on upload.

I don't know how safe it is to depend on the PLATFORMINFO$("ID") for replacing files though, you can make a game just to harvest PLATFORMINFO$("ID") from users and then you get a complete list of passwords into your database... I would create a password on the server and send back to the user on first upload, save it on disk and when reuploading using that password automaticly.

Good luck with your project, looking forward to play it!

Kitty Hello

I don't know about the boundary random numbers. All samples do it that way.