The online racing simulator
PHP4/5 - LFSWorldSDK, class for stats retrieval
(288 posts, started )
Quote from bdshan :Filur, indulge me if you will. What does

preg_match_all('/(.+)\s(.+)\s(.+)\s(.+)/U', $data, $fragment);

do? This line is the only line in the PB function that I don't understand. And forgive me I have not looked up the preg_match function in the PHP manual.

It matches any character the least possible amount of times until it stumbles upon a whitespace character, four times, then it matches any character until there's nothing more to match. The parenthesis creates subpatterns / submatches which end up as entries in the $fragment array.

Result should be the same as explode(' ', $string, 5);
Awe! I missed the great discussion! Sorry all, I've been doing a lot of work, did not even have thanksgiving off. Anyway, back on subject. I think a simple redo of all of this would be nice. filur has wanted to get a pure php 5 version of this out for some time. I'd like to update the current php 4 version quite alot so that it works better. Seems I've not put alot of testing into it, or as much as I should of.
LFSWorld 1.5.Beta (UNI)

<?php 
php
    
// Copyright 2006 Mark 'Dygear' Tomlin & Mikael 'filur' Forsberg
    // Verison 1.5.beta (UNI)
    
class LFSWorldSDK {
        function 
LFSWorldSDK($idk$ver 1.3$pre false) {
            
$this->ps $pre;
            
$this->idk $idk;
            
$this->version $ver;
        }
        function 
make_query($qryStr) {
            return 
file_get_contents("http://www.lfsworld.net/pubstat/get_stat2.php?version={$this->version}&idk={$this->idk}&ps={$this->ps}{$qryStr}");
        }
        function 
get_hl($racer) {
            foreach (
explode("\n"$this->make_query("&action=hl&racer={$racer}")) as $line => $data)
                list(
$result[$line]['id_hl'], $result[$line]['track'], $result[$line]['car'], $result[$line]['split1'], $result[$line]['split2'], $result[$line]['split3'], , $result[$line]['time'], $result[$line]['flags_hlaps']) = split(' '$data8);
            return 
$result;
        }
        function 
get_pb($racer) {
            foreach (
explode("\n"$this->make_query("&action=pb&racer={$racer}")) as $line => $data)
                list(
$result[$line]['track'], $result[$line]['car'], $result[$line]['time'], $result[$line]['lapcount']) = split(' '$data4);
            return 
$result;
        }
        function 
get_ch($track$car$control null) {
            foreach (
explode("\n"$this->make_query("&action=ch&track={$track}&car={$car}&control={$control}")) as $line => $data)
                list(
$result[$line]['split1'], $result[$line]['split2'], $result[$line]['split3'], $result[$line]['time'], $result[$line]['flags_hlaps'], $result[$line]['racername']) = split(' '$data6);
            return 
$result;
        }
        function 
get_wr() {
            foreach (
explode("\n"$this->make_query("&action=wr")) as $line => $data)
                list(
$result[$line]['id_wr'], $result[$line]['track'], $result[$line]['car'], $result[$line]['split1'], $result[$line]['split2'], $result[$line]['split3'], $result[$line]['time'], $result[$line]['flags_hlaps'], $result[$line]['racername']) = split(' '$data9);
            return 
$result;
        }
        function 
get_pst($racer) {
            list(
$result['distance'], $result['fuel'], $result['laps'], $result['hosts'], $result['wins'], $result['second'], $result['third'], $result['finished'], $result['quals'], $result['pole'], $result['online'], $result['drags'], $result['drag'], $result['online'], $result['hostname'], $result['time'], $result['track'], $result['car']) = split("\n"$this->make_query("&action=pst&racer={$racer}"), 19);
            return 
$result;
        }
        function 
get_hosts() {
            
$string $this->make_query("&action=hosts");
            for (
$pointer 0$i 0$pointer <= strlen($string); $i++) {
                
$NumberOfRacers = @unpack("c"substr($string$pointer 521));
                
$NumberOfRacers $NumberOfRacers[1];
                
$NumberOfRacersLen $NumberOfRacers 24;
                
$PointerPast $NumberOfRacersLen 53;
                
$result[$i] = array();
                if ((
$result[$i] = @unpack("a32hostname/A4tmlt/A4tcrm/icars/irules/claps/cqual/cspare1/cspare2/cnrofracers/a{$NumberOfRacersLen}racernames"substr($string$pointer$PointerPast)))) {
                    
$result[$i]['racernames'] = preg_split("/\\0/"$result[$i]['racernames'], -1PREG_SPLIT_NO_EMPTY);
                    
$result[$i]['tmlt'] = unpack("ctype/cmain/a1letter/ctestId"$result[$i]['tmlt']);
                    
$result[$i]['tcrm'] = unpack("ctrack/cconfig/creversed/cmax"$result[$i]['tcrm']);
                }
                else
                    unset(
$result[$i]);
                
$pointer += $PointerPast;
            }
            return 
$result;
        }
        function 
get_teams() {
            
$string $this->make_query("&action=teams");
            for (
$pointer 0$i 0$pointer <= strlen($string); $i++) {
                
$infoLen = @unpack("S"substr($string$pointer 2982));
                
$infoLen $infoLen[1];
                
$nrMembers = @unpack("S"substr($string$pointer 300 $infoLen2));
                
$nrMembers $nrMembers[1] * 24;
                
$PointerPast 302 $infoLen $nrMembers;
                
$result[$i] = array();
                if ((
$result[$i] = @unpack("a128team/a6tag/a32country/a128url/Ibits/Sinfo_len/a{$infoLen}info/Snr_members/a{$nrMembers}members"substr($string$pointer$PointerPast)))) {
                    
$result[$i]['members'] = preg_split("/\\0/"$result[$i]['members'], -1PREG_SPLIT_NO_EMPTY);
                    
$result[$i]['info'] = urldecode($result[$i]['info']);
                }
                else
                    unset(
$result[$i]);
                
$pointer += $PointerPast;
            }
            return 
$result;
        }
        function 
get_hl_log($log_filter 4$lines 150$control null$starttime 0) {
            
$string $this->make_query("&action=hl_log&log_filter={$log_filter}&lines={$lines}&control={$control}&starttime={$starttime}&format=3");
            for (
$pointer 0$i 0$pointer <= strlen($string); $i++) {
                
$result[$i] = array();
                if ((
$result[$i] = @unpack("itime/a24racer/a32country/A4tcrc/i4split/Spos/Sflags/iid_hl"substr($string$pointer88))))
                    
$result[$i]['tcrc'] = unpack("ctrack/cconfig/creversed/ccar"$result[$i]['tcrc']);
                else
                    unset(
$result[$i]);
                
$pointer += 88;
            }
            return 
$result;
        }
    }
?>

This should be faster as it does not rely on preg for it's string parsing in all cases.
This is not directly compatible with the older versions due to the fact that I've changed around the order of the args for the entry function.
Preamble
I'd like to point out that code IS BETA, and as such I'm going to be building on it, alot. By alot, I mean to the level where you only need one function from the SDK to get just the data you need. I also plan on adding some support files to facilitate functions that would allow you to make queries like so :

From my brain to your server

<?php 
$SDK
->get_pb('Dygear''BL1''BF1');
?>

I know, I know, woop-de-do. I can use the short name for the track, oh but there's so much more planed . . .


<?php 
$SDK
->get_pb('filur', array('BL1''FE4''KY3''661'), array('S-S''-FOX'));
?>

Notice that I'm going to be getting all of filur's pbs on Blackwood GP, Fern Bay Black, Kyoto GP Long, and Aston North Reverse with the S-S class of cars minus the FOX. Now this is not code that I've made ... yet, but this is what is planed.

HorsePower
See what you started HorsePower? I just have to one up ya man, and this is the best way I know how, to out program ya. BTW, I still think your program is slick, I would love to run it, but I can't bring myself to. I might find something in your code that makes me want to use it. And I'd really like this project to be from my brain (and filur's brain) to your computer.

The Keep It Simple Stupid Paradox.
I build on the original LFSWorldSDK with the idea that it should not do more then was needed, but I was missing the point. It, as is, is nothing more then an API to facilitate easy access to the LFS World Repository. I wanted to keep it simple on the LFSWorldSDK side so it would be as lean, and thus fast. But there is the issue, people who are using this product really want something to do it all, they really want an SDK. I've failed to provide that up to this point. It's not filur's fault, he was pushing me in the direction of a full on do it all SDK but I stopped him and said K.I.S.S. The issue with K.I.S.S is that it's anything but simple for the guy who has to make the data do it's little work out. They where not really gaining anything from the SDK other then a nice way to connect and get data from the LFSWorld website. Pointless.

Design ideas
This is what I'm planing, 1 file for each current function of the SDK. The core file will still be called LFSWorldSDK and will do all of the query work, it will also be the entry point for the SDK as is right now in 1.5.Beta. 1.5.Beta is dead, I'm not going to be doing any more work on it. The next step is 2.0, what 1.0 should of been, and what filur has always wanted. A real SDK. It will do it all. One line queries to get the data you need. Directory Structure Looks Like This (Tentative), and the

LFSWorldSDK.php // LFSWorldSDK Entry Point (You Include This File, It Includes The Rest).
support_core.php // Home to things like support arrays with info on what is equal to what.
action_hl.php // Hot Lap query code.
action_pb.php // Personal Best query code.
action_ch.php // CHart query code.
action_wr.php // World Record query code.
action_pst.php // Personal STatistics query code.
action_hosts.php // Hosts query code.
action_teams.php // Teams query code.
action_hl_log.php // Hot Lap Log query code.

File(s) are only going to be included if you use that function.
Quote from Dygear :
HorsePower
See what you started HorsePower? I just have to one up ya man, and this is the best way I know how, to out program ya. BTW, I still think your program is slick, I would love to run it, but I can't bring myself to. I might find something in your code that makes me want to use it. And I'd really like this project to be from my brain (and filur's brain) to your computer.

Hehe. I really would like if you bring your work to the next level.

And if you have a really weak moment, and decide to implement my tracker into your SDK, I would love it.
Your design ideas are very similar to how PPF is architectured but I no longer have time to update it since taking a full time PHP developer position.
Rather than going to waste, perhaps it can be of some use to you, even if you don't use the code directly.

PPF is architectured like this ...
  • Tailored Interfaces
    Simple implementation specific interfaces for an end programmer to use that will provide an easy way to 'do it all' without compromising the core code. (Only the SimpleInterface is implemented at present which provides a very similar API to LFSWorldSDK)
  • Generic Interface
    This handles all requests to the 'providers' (explained below). It takes care of default params, caching, handling the request etc. It is completely isolated from a specific provider so that if multiple sources for LFSWorld data pop up in the future then only a new provider needs to be written.
  • Providers
    The base provider handles the HTTP request, decompression and error checking. The sub-providers take care of the parsing and (like in your ideas) are each in their own file.
  • CacheProviders
    Cache providers provide different means of caching. There is currently a file based cacher and an sql based cacher (only works with hosts atm because the other providers need pre-insert and post-select callbacks writing for them.
  • Lookups
    The lookups provide a means of translation between common formats that LFSWorld uses, and that someone using PPF might need. i.e. convert XFG->XF Gti or 000 -> Blackwood GP
  • Utils
    These are small bits of code that perform useful functions. For example, there is an extremely optimized Colour and Charset parser with output callbacks so that only a callback needs to be written for a new kind of output. (CSS & HTML ones exist already)
    Other functions include ones to decompress LFSWorld data, check for LFSWorld errors and make LFSJoin/Join2LFS compatible links.
A quick overview of how these fit together looks like this...
User creates TailoredInterface (which extends GenericInterface)
User calls a 'do it all' method on the TailoredInterface
TailoredInterface creates provider instance
TailoredInterface creates cache provider instance
TailoredInterface does pre-request magic
TailoredInterface calls GenericInterface to make a data request and passes provider & cache objects to GenericInterface
GenericInterface checks with provider that all params are set
GenericInterface checks with cache to see if a valid entry exists
Assuming no cache entry, GenericInterface calls provider to get data
Provider makes request to LFSWorld
Provider decompresses data and checks for errors
Provider returns data to Generic Interface
Generic interface passes returned data to cacher
Cacher stores data
Generic Interface returns data to TailoredInterface
TailoredInterface does post-request magic (display and such)
The End

If any of that interests you, feel free to pm me for further explanation.
If not then maybe someone else might find use it.
Attached files
lfswppf2a.zip - 643.7 KB - 193 views
I've looked at PPF twice i think, but as i can't stand PEAR i've never tried it.

My own design is something like ..


Wrapper creates Query instance.
Wrapper passes Query instance to Engine.
Engine grabs request details from Query, performs request and decompression.
Engine passes raw result to Query.
Engine calls interfaced methods in Query.
Query formats itself.
Engine returns Query (result) to Wrapper
Wrapper returns Query (result) to User.

The stuff i've written is highly object oriented, meaning you won't get hotlap flags returned as "517", you'll get something like ..

HotlapFlags Object {
bits => 517
gearblip => TRUE
gearcut => FALSE
...
__toString() => 'w gb ac'
}

.. etc.

Typical use would be something like ..

$wrapper = new LFSWorldSDK($idkey, $compression);
$hotlaps = $wrapper->getHotlaps('filur');

foreach ($hotlaps as $lap)
echo $lap->track.' '.$lap->car.' @ '.$lap->laptime.'<br />';

print_r($hotlaps->_asArray());

echo $hotlaps->getSingleLap('BL1', 'LX6')->laptime;

I've also got Resources which are pretty similar to Lookups, mine are collections of static methods:

$readable = LFSWLaptime::getTrimReadableTime($integer_laptime);
$car_fullname = LFSWCars::getCarName($car);

Where, for example, getCarName() always returns the full name whether you call it with car bits or the abbreviation (or the full name, even).

This will of course not run under PHP4 at all, has __toString() design issues with some versions of PHP5, but should hopefully run as intended on PHP6. I am however writing it under PHP5, and it works good enough. Currently backing this thing with no issues thus far.
Quote from filur :I've looked at PPF twice i think, but as i can't stand PEAR i've never tried it.

Sounds good, although watch out for speed issues on the charts if you are creating objects for each record. My initial solution was entirely OO but I found it to be much slower than arrays for the dataset with so many objects, even on PHP5. (Unless the charts output has changed; it's been a while since I tested)

PPF will run under PHP4 (>4.2.0 I think, although maybe 4.3), and fair point about PEAR. It's pretty crap but is only used for HTTP_Request (because fopen can't do timeouts and I couldn't be arsed to write my own HTTP client) and Cache_lite for the file cache.

Files that require PEAR are (IIRC) ppf/caches/lfsworldfilecache.php && ppf/providers/lfsw/baseprovider.php ... possibly some hackish variable poking and stuff in the sub-providers.
It would be pretty easy to port it to other libs since there aren't that many references made to PEAR specifics.

Yeah, anyway, point wasn't in the architecture; that was just provided so that you might be able to find the stuff you want. You can rip it apart and mangle it about as much as suits you, or just use a single line of code if it is useful.

I forgot to mention, ignore the license. I'm essentially opening this up for a free for all so no credit or anything silly is required.
Quote from Anarchi-H :... watch out for speed issues on the charts if you are creating objects for each record.

It'll be slow(er), no doubt. Hopefully countered a bit by ease of use.

Quote from Anarchi-H :... about PEAR. It's pretty crap but is only used for HTTP_Request (because fopen can't do timeouts and I couldn't be arsed to write my own HTTP client) and Cache_lite for the file cache.

I think there's one or more .ini settings for socket timeouts, but i can see why you'd use PEAR stuffies. I'd probably be a PEAR user myself if the contents weren't distributed thru and / or relied on PEAR.
Quote from filur :I think there's one or more .ini settings for socket timeouts, but i can see why you'd use PEAR stuffies. I'd probably be a PEAR user myself if the contents weren't distributed thru and / or relied on PEAR.

There is indeed but I wanted it to be compatible with shared hosts.
I'm far from a PEAR user. They just happened to be on hand when I did the prelims.
@filur
I like your code, and it does do alot, but I don't like how it seems to much like Java or C#. It also has issues in the areas such as no, and I do mean NO, support for the PHP4 people out there. So that pretty much makes it a goner. Other then that, really it's sweet.

@Anarchi-H
I would think in a more required files point of view, then an extras point of view. I'd like it if each file could be used by itself as a parser, or a fetcher, or even a helper.

Name:LFSWorldSDK.php
Type:Fetcher
This file simple makes contact with LFSWorld and get's the data from it, uncompressing the data if necessary. This file would also contain the prototypes for the parsers and throw a warning if that function (by the file not being there) is not available. I'd like to point out, that it's not meant to be used with out all of the files, but if the user should use it that way, then I must have something in place that will stop a program halt in that case.

Name:LFSDataConvert.php
Type:Helper
This is not a required file, but if you don't have it, it will not be able to convent things like 'BL1' or 'Blackwood GP' into '000', or 'BMW SAUBER' into 'BF1'.

Name:action_hl.php
Type:Paser

Name:action_pb.php
Type:Paser

Name:action_ch.php
Type:Paser

Name:action_wr.php
Type:Paser

Name:action_pst.php
Type:Paser

Name:action_hosts.php
Type:Paser

Name:action_teams.php
Type:Paser

Name:action_hl_log.php
Type:Paser

This is pretty much what I am thinking. No file is needed, but all files would help. I have four days I've given myself to program this, starting on my birthday (Dec 16th) but it should only take me two days. filur, it looks like I'm making the PHP4 version again. If you want to do the PHP5 (more OOP less panzyness) version in a concurrent manner along with me, that would be cool. If not that's fine.
Quote from Dygear :filur, it looks like I'm making the PHP4 version again. If you want to do the PHP5 (more OOP less panzyness) version in a concurrent manner along with me, that would be cool. If not that's fine.

Sure, i'll just go rewrite it like 8 more times.

Or maybe I should just fill the cvs with whatever i've got and actually try to finish it.

Edit: btw, i like the (your) new ideas.
Quote from filur :Sure, i'll just go rewrite it like 8 more times.

Or maybe I should just fill the cvs with whatever i've got and actually try to finish it.

Edit: btw, i like the (your) new ideas.

@filur
Yea, they are kinda stolen from yourself are'ent they.

@all
Now if only I had the sense to listen to him in the first place .
Not quite sure I get what you mean, but if you are planning what I think you are planning, then it's very similar to PPF.

You could include just one of the providers and it would in turn require the base provider, but apart from that it could be used stand alone. I think that is what you are planning isn't it?

The only difference I see is that in PPF the fetcher resides in an inherited base provider class where as yours resides alone.

...unless you were going for the procedural approach and by 'requiring' the files they executed automatically. Still, similar approach, different implementation.
Quote from Anarchi-H :You could include just one of the providers and it would in turn require the base provider, but apart from that it could be used stand alone. I think that is what you are planning isn't it?

Not quite, the 'providers' or parsers only require the string they need to parse, they are stand alone completely. The core of it could be used stand alone just to get data from LFS World, as it would handle all of the compression stuff, and just return the string. If you wanted to use one of the functions as part of the SDK the files need to be there in order for it to work, or it will raise an error and return a FALSE.
Hi, so guys how does it (LFSWorldSDK) look like?, not in codes just in one of webpages or a pic. thanks
Quote from Jonas8431 :Hi, so guys how does it (LFSWorldSDK) see?, not in codes just in one of webpages or a pic. thanks

If i'm not mistaken, you're asking for an example of what LFSWorldSDK looks like when used on a webpage? Simple answer is it doesn't look like anything at all. It works sort of like database communication functions, hopefully letting you grab the information you want and present it in the way you want to.

LFSWorldSDK creates no page output whatsoever, it only helps gather data so you can create the page yourself.
So, is there anything like an official last version of LFSWorldSDK? A friend of mine handed it to me, and googling for it I found a sourceforge site where the last versión i found was actually older to the one my friend gave me. Is there an official site for it? And what is the last version?

Thnx for this work, it really helps
Quote from MaKaKaZo :So, is there anything like an official last version of LFSWorldSDK? A friend of mine handed it to me, and googling for it I found a sourceforge site where the last versión i found was actually older to the one my friend gave me. Is there an official site for it? And what is the last version?

Thnx for this work, it really helps

http://lfsworldsdk.cvs.sourcef ... /lfsworldsdk/lfsworldsdk/

Look at the 1.2 file, it's the most update to date one. (lfsworldsdk.php not the LFSWorldSDK.php)
So ... what do ya guys think? Any info, bugs, or anything would be nice. Feedback, I need feedback!
Nothing of use. I do have a bug where by it will occasionally throw warnings about not enough bytes for unpack to run properly (yet it's clearly getting results), but I've not had the opportunity to find out why it's telling me that yet. I'm in the process of moving my server, so the script currently hasn't been running for the last few days.
Quote from the_angry_angel :Nothing of use. I do have a bug where by it will occasionally throw warnings about not enough bytes for unpack to run properly (yet it's clearly getting results), but I've not had the opportunity to find out why it's telling me that yet. I'm in the process of moving my server, so the script currently hasn't been running for the last few days.

Funny, I could of swore I fixed that! You should be getting that in the hosts list. I'll double check that function.

... well I can't right now, we just got a 911 call ... I'll check it in an hour or so.
Simple fix, change this:
a32hostname/a4tmlt/a4tcrm/icars/irules/claps/cqual/cspare1/cspare2/cnrofracers/a{$NumberOfRacersLen}racernames

to this:
a32hostname/A4tmlt/a4tcrm/icars/irules/claps/cqual/cspare1/cspare2/cnrofracers/a{$NumberOfRacersLen}racernames

Yeah, it turns out it throws a hissy fit more times with the lowercase a then with the uppercase A. *srugs*. One line fix.


[edit]Huh, tcrm wants an lower case a while tmlt wants an upper case A. Oh joy! This get's fun from time to time. [/edit]
Fix one of my fixes. Will update the cvs once I get out of work (Or I get out of school [about 12 hours from now]). But yeah, this is proof! I really do eat my own dog food.

PHP4/5 - LFSWorldSDK, class for stats retrieval
(288 posts, started )
FGED GREDG RDFGDR GSFDG