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:
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):
See next post for code :)
---------------------------
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]
#endifSee next post for code :)
About the author
Game developer.
Recent Threads
#2
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:
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.zipUnpack 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
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.
:)
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
the full feature list will come together with the resource
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
As stated on first post:
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.
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
Can someone explain it a bit for me?
TnX :P
JoZ
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
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).
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
The errors all look like this but all different libraries...
__stricmp already defined in LIBCMTD.lib(stricmp.obj)
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
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.
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.
#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
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:
Good luck!
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!
Associate Fyodor -bank- Osokin
Dedicated Logic
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.