Game Development Community

Libcurl implementation

by Fyodor -bank- Osokin · in Torque Game Engine · 11/17/2007 (7:44 pm) · 63 replies

this is my own libcurl implementation into TGE 1.5.2 "as is". Feel free to use it for your own games.
---------------------------
What implemented:
download works for http and ftp (without login/password - tested)
download works for http and ftp (with login/password - not tested)
you can set to resume previously interrupted download/upload (tested)
you can cancel download (tested)
upload works, but not tested heavily
---------------------------
See the available script functions.
If you need upload, don't forget to call .setUploadFlag(true); :)

---------------------------
Instructions:

Download "full" curl package (I've used 1.17.1).
Unpack it into /lib/ folder (so you have the /lib/curl-7.17.1/* )

For "Torque Demo" (or your own project):
Add ../lib/curl-7.17.1/include to the project's "Addition Include Directories".
Add ;CURL_STATICLIB to project's "Preprocessor Definitions".
Add ;"../lib/curl-7.17.1/lib/Release" to project's "Additional Library Directories".
Add curllib.lib to project's "Addition Dependencies".
Add libcmt.lib to project's "Ignore Specific Library" (without this it won't link the EXE).

Add lib/curl-7-17-1/lib/curllib.vcproj project file into your solution.

In curllib project's properties:
Add ";../../zlib" to the project's "Addition Include Directories".
Add ";HAVE_ZLIB_H;HAVE_ZLIB;HAVE_LIBZ;CURL_STATICLIB;CURL_DISABLE_LDAP" to the "Preprocessor Definitions".

engine changes:
game/main.cc:
at the top where all other includes are, add:
#include "sim/simCurl.h"
change the beginning of initLibraries function:
static bool initLibraries()
{
   [b]if (!SimCurl::initialize())
   {
      Platform::AlertOK("libcurl Error", "Unable to initialize the libcurl... aborting.");
      return false;
   }
[/b]
   if(!Net::init())
   ...
change the end of shutdownLibraries function:
Net::shutdown();
   [b]SimCurl::deinitialize();[/b]
}

Rebuild the "curllib" project.
Rebuild your project (Torque Demo).

If it don't compile and giving errors like "templates can't be used when "C" linkage.." for wspiapi.h (see this page for explanation), you need to change the curl/curl.h file in includes folder (changes in BOLD):
#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H))
/* The check above prevents the winsock2 inclusion if winsock.h already was
   included, since they can't co-exist without problems */
  [b]#ifdef __cplusplus
    }
  #endif[/b]
#include <winsock2.h>
#include <ws2tcpip.h>
  [b]#ifdef __cplusplus
    extern "C" {
  #endif[/b]
#endif

See next post for code :)
Page «Previous 1 2 3 4 Last »
#1
11/17/2007 (7:45 pm)
Here is a my "script helper" routine:
if (!isObject(curler)) new ScriptGroup(curler);

/// Add new task for "binary" download (physical file on HDD)
/// %expectedSize and all params after are optional
function curler::addDownloadTask(%this, %url, %file, %expectedSize, %md5, %cbOK, %cbFailed, %cbProgress)
{
   %id = getStringMD5(%url);
   %sc = curler.findObjectByInternalName(%id);
   if (!isObject(%sc))
   {
      %sc = new SimCurl() { internalName = %id; };
      %this.add(%sc);
      %sc.setURL(%url);
      %sc.url = %url;
      if(%expectedSize<1) // we treat "", "0" or "-1" as "unknown"
         %sc.fileSizeExp = -1;
      else
         %sc.fileSizeExp = %expectedSize;
      %sc.setFileName(%file);
      %sc.file = %file;
      %sc.hash = %md5;
      // Callbacks
      %sc.callBack = %cbOK;
      if(%cbOK!$="")
         %sc.setFinishCallback(%cbOK);
      %sc.callBackFail = %cbFailed;
      if(%cbFailed!$="")
         %sc.setFailedCallback(%cbFailed);
      %sc.callBackProgress = %cbProgress;
      if(%cbProgress!$="")
         %sc.setProgressCallback(%cbProgress);
      %sc.ts = getSimTime();
      %sc.runCount = 0;
      if (isFile(%file) && fileSize(%file)==0)
      {
         if($curl::debug) error("Removing zero-sized file!");
         deleteFile(%file);
      }
      if (isFile(%file))
      {
         if($curl::debug) error("File exists!!!");
         %fileSize = fileSize(%file);
         if(%sc.fileSizeExp > 0 && fileSize(%file)==%sc.fileSizeExp)
         {
            if($curl::debug) error("We know the expected file size and file sizes are the same!");
            if(%sc.hash!$="" && strlwr(%sc.hash)$=getFileMD5(%file))
            {
               if($curl::debug) error("File sizes is the same and hashes too! Nothing to do!");
               schedule(10, 0, %sc.callBack, %sc);
               return true;
            }
            else
            {
               if($curl::debug) error("File size is the same, but hash is different! Deleting file!");
               fileDelete(%file);
            }
         }
         if(%sc.fileSizeExp > 0 && fileSize(%file) > %sc.fileSizeExp)
         {
            if($curl::debug) error("Existing file is greater than we expecting! Deleting file!");
            fileDelete(%file);
         }
         if(%sc.fileSizeExp > 0 && fileSize(%file)>0 && fileSize(%file) < %sc.fileSizeExp)
         {
            if($curl::debug) error("Setting resuming from:" SPC fileSize(%file));
            %sc.resumingFrom = fileSize(%file);
            %sc.setResumeFrom(%sc.resumingFrom);
         }
      }
      %sc.start();
      return true;
   }
   else
   {
      error("The download is already in progress... not starting a new one!!!");
      return false;
   }
}

/// Add new "text" task for download (useful for XML or plain-text ie no physical file but using buffer instead
function curler::addTextTask(%this, %url, %cbOK, %cbFailed, %cbProgress)
{
   %id = getStringMD5(%url);
   %sc = curler.findObjectByInternalName(%id);
   if (!isObject(%sc))
   {
      %sc = new SimCurl() { internalName = %id; };
      %this.add(%sc);
      %sc.setURL(%url);
      %sc.url = %url;
      // Callbacks
      if(%cbOK!$="")
         %sc.setFinishCallback(%cbOK);
      if(%cbFailed!$="")
         %sc.setFailedCallback(%cbFailed);
      if(%cbProgress!$="")
         %sc.setProgressCallback(%cbProgress);
      %sc.ts = getSimTime();
      %sc.start();
      return %sc;
   }
   return false;
}

Usage:
function cbOK(%sc)
{
   echo(%sc SPC "Finished OK");
   %sc.delete();
}
function cbError(%sc)
{
   echo(%sc SPC "Not Finished, status:" SPC %sc.getStatus() );// Will return curl error code, like canceled or any other error code
   %sc.delete();
}
function cbProgress(%sc)
{
   echo("Downloading, currently at" SPC ((%sc.getSizeNow()/%sc.getSizeTotal())*100) @ "%");
);
}
function startBinaryDownload()
{
   curler.addDownloadTask("http://www.garagegames.com/", "starter.fps/cache/gg_index.html, -1, "", "cbOK", "cbError", "cbProgress");
}
function cbFinishedText(%sc)
{
   while(!%sc.isEOF())
   {
      %line = %sc.readLine();
   }
   %sc.delete();
}
function startTextDownload()
{
   curler.addTextTask("http://www.garagegames.com/", "cbFinishedText", "cbError", "cbProgress");
}

Important: Don't forget to delete SimCurl object in both callbacks (OK/failed).

For the code itself see below.
#2
11/17/2007 (7:48 pm)
due to the forum limitations the code is packed in zip and available for download separately: www.afterworld.ru/gg/simCurl.zip
Unpack in engine/sim/ folder, add both files into your project, adjust main.cc as per instructions in first post and rebuild.

Available script functions:
SimCurl.setURL(%url);
// Set URL to download/upload from/to

SimCurl.getURL();
// Retrieve url, just in case :)

SimCurl.setLogin(%loginName);
// sets login name for use in authentification

SimCurl.setPassword(%password);
// sets password for use in authentification

SimCurl.setFileName([%fileName]);
// If specified, the contents will be downloaded into physical file,
// otherwise still be stored in a buffer

SimCurl.setUploadFlag(%flagBool);
// you can set "upload" flag with this

SimCurl.perform();
// Start processing now without using threads, useful for debugging

SimCurl.start();
// Actually, start the download after everything is setup

SimCurl.isRunning();
// Returns true/false if it still running/working downloading/uploading)

SimCurl.getStatus();
// Get status of download (after complete/failure), see curl.h for error codes

SimCurl.readLine();
// Read line from buffer, can be used to parse TEXT/XML without storing files on HDD

SimCurl.isEOF();
// Used with .readLine() method to catch the end of buffer

SimCurl.getSizeTotal();
// Returns INT for the size of file being downloaded/uploaded.
// Will return zero if not (yet) known.

SimCurl.getSizeNow();
// Returns the "already-downloaded" amount of bytes

SimCurl.cancelDownload();
// Cancel currently downloaded file, will trigger callback for failure,
// so grab .getStatus() to catch cancellations

SimCurl.setResumeFrom(%bytes);
// You can tell curl to resume download/upload by specified bytes.

SimCurl.setProgressCallback(%funcName);
// Set the function name to call during downloading progress

SimCurl.setFinishCallback(%funcName);
// Set the function name to call when finished downloading (success)

SimCurl.setFailedCallback(%funcName);
// Set the function name to call when download failed or cancelled
#3
11/18/2007 (11:58 am)
We integrated libcurl into our codebase. All that work was done by Lateral Punk.

It was pretty straightforward, I think, and uses an object much like yours above to expose it's functionality
into script (with script callbacks "onError" and "onDone" that get called on your curl request at completion).

Slightly less straightforward is that it runs the libcurl layer on a background thread and uses curl's "multi-handles". All the network activity is handled via buffers that are synchronized on (so that you can get the data from a partially completed HTTP GET, for example).

It works really well, and we even have https support. We use it for tons of stuff from script.

I don't really think I can post the code though.

Anyways, I'll let Krunal expound on it further, or if you had specific question, I'm sure he could answer them.
:)
#4
12/01/2007 (10:14 am)
I'll post soon a working version of this.
the full feature list will come together with the resource
#5
12/01/2007 (12:29 pm)
Thanks bank, really great work
#6
12/01/2007 (12:54 pm)
First, second, third, forth (hm?) posts updated with current version.
As stated on first post:
Quote:Later will work on proper resource / documentation and fully-funcitonal script wrapper for easy maintaining the download/uploads

P.S. We don't need SSL (https), so we don't even bothered to implement the support for it, but I think it should be quite easy - include library, set flags/defines and you go.
#7
12/05/2007 (3:26 am)
From a nOOb point of view, what is libCurl? <8-P
#8
12/05/2007 (3:33 am)
From: http://curl.haxx.se/libcurl/
Quote:libcurl - the multiprotocol file transfer library

libcurl is a free and easy-to-use client-side URL transfer library, supporting FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS and FILE. libcurl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, Kerberos4), file transfer resume, http proxy tunneling and more!

libcurl is highly portable, it builds and works identically on numerous platforms, including Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS and more...

libcurl is free, thread-safe, IPv6 compatible, feature rich, well supported, fast, thoroughly documented and is already used by many known, big and successful companies and numerous applications.
#9
12/06/2007 (4:51 am)
Not really understanding what this resource does...

Can someone explain it a bit for me?

TnX :P

JoZ
#10
12/07/2007 (10:58 am)
If you want your Torque to download/upload data from/to HTTP/FTP servers, you will need something like libcurl to be integrated into engine.
By default, Torque really lacks on the a question of support proper web transfer. There is HTTPObject (TCP derived) but it can be used only for downloading "text/ascii" data from web, and it's not threaded (runs on the same thread as whole simulation) - so if there is a delay with reply from server, your game is going to froze for a bit.

The current implementation of libcurl gives you "enhanced" downloading (and uploading) of data. You can tell engine to download 10MB file and still play around - it should not affect "heavily" on the performance (depending on your computer / internet connection).
#11
12/13/2007 (4:58 pm)
Thanks a ton for this bank.
#12
05/07/2008 (9:02 am)
I am getting a bunch of errors when I compile the Torque Engine using this code.

The errors all look like this but all different libraries...

__stricmp already defined in LIBCMTD.lib(stricmp.obj)
#13
11/21/2008 (6:52 am)
I asked over on the mac forum so I thought I would ask here if someone has ported this to compile on mac/xcode and if so, what the gotchas might have been. Thanks in advance!
#14
11/21/2008 (10:34 am)
Good job Bank.
#15
01/27/2009 (12:39 pm)
The code is updated, please see the first three posts.
The current version is much more "productive" as I've added support for callbacks and resuming downloads.

The scripted routine is re-written too -- to work together with callbacks.

Again, this is made for TGE 1.5.2, but as far as I see, it can be semi-easily be ported to TGEA 1.7.x or 1.8.
#16
01/27/2009 (12:43 pm)

Hey, great work, bank.
#17
01/27/2009 (1:22 pm)
bank, this technology was one I became quite dependent on for some crucial game functionality, I want to thank you for posting the original, and this update! It made some awesome things possible.
#18
01/29/2009 (9:11 am)
Thanks bank, i think that i can use this for a ingame forum or web chat..., stay tunned... :D
#19
03/12/2009 (5:00 pm)
This is going to be the backbone of my db communication and live updates. You saved me days of work! Thank you very much.
#20
03/13/2009 (4:34 am)
Just a quick note about my adventure to get this into TGEA 1.7.1. - probably works in 1.8.1 too.

Add LIBCMT (possibly already there) and LIBCMTD to your project's "Ignore Specific Library" list.

Instead of curllib.lib, add libcurl.lib to your project's "Additional Dependencies". That's the only lib you'll find.

The engine changes must go into app/mainLoop.cpp into StandardMainLoop::init() and StandardMainLoop::shutdown() - find the Net::init() and Net::shutdown() functions.

I got it to compile in DEBUG mode as well. To be able to use it in both Debug and Release, add "../lib/curl-7.17.1/lib/Debug" - or something along these lines depending on your curl version - to your project's "Additional Library Directories" list. The curl version I used was 7.19.4. Do not forget to check both simCurl.h and simCurl.cpp and remove the empty constructor and destructor ifdef stuff for debug mode!

I also found a bug of some sort: it is possible that when (or soon after) deleting the object, perform() gets called. _G.head never evaluates to false even after the deletion, so that was causing me some crashes.

The solution was that I made sure that _G.head is NULL-ed in the destructor, and in threadFunction I call _G.head->next only if _G.head is not null. This has solved all my issues. In code:

SimCurl::~SimCurl()
{
	if(storagedesc.mem.ptr && SIMCURL_STORAGE_BUFFER)
		dFree(storagedesc.mem.ptr);
	if (_G.head)
		_G.head = NULL;
}

void SimCurl::threadFunction(S32 n)
{
	(void)n;
	while(_G.head) {
		_G.head->perform();
		Mutex::lockMutex(_G.mutex);
		if (_G.head)
			_G.head = _G.head->next;
		Mutex::unlockMutex(_G.mutex);
	}
}

Good luck!
Page «Previous 1 2 3 4 Last »