kelvinluck.com

a stroke of luck

Logging from FAME


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

I’ve recently started playing with FAME (or more properly FME - FDT, MTASC and Eclipse) and found it frustrating not having a trace window in Eclipse which I could use to debug my code…

Then I discovered Powerflasher SOS which is a handy standalone console which you can connect to via an XMLSocket from Flash. SOS has some incredible handy features for use along with FME. For example you can set it to “Console > Bring to Front > During Connection”. This means that the logging console will stay at the front of your screen while there is a socket connection open to it. I found it fiddly with other logging solutions which had a console opening within a panel in Eclipse (maybe because of the lack of screen real estate on my laptop). So I like this feature a lot. You could even run the SOS console on a different machine to make maximum use of screen space!

I thought what if I could combine the power of SOS with the convenient object introspection of the LuminicBox logger and colour coding etc.. And while I was at it I decided to also add support for the MTASC TRACE directive. So I did. Building on the TracePublisher from LuminicBox and adjusting a little code I came up with something which turns this:

/**
* example code for using a com.kelvinluck.util.SOSLogPublisher
**/


import com.kelvinluck.util.SOSLogPublisher;
import com.kelvinluck.util.LogWrapper;

LogWrapper.getInstance().init();
LogWrapper.getLog().addPublisher(new SOSLogPublisher("myAppsName"));
// log messages at different levels
LogWrapper.getLog().fatal("This is a fatal error :'(");
LogWrapper.getLog().error("This is an error :(");
LogWrapper.getLog().warn("This is a warning, warning, warning");
LogWrapper.getLog().info("This is information");
LogWrapper.getLog().debug("This is debugging info");
// an Array (folded by default in SOS)
LogWrapper.getLog().debug([1,2,3,{a:"Part A", b:"Part B"}, "Some text", 99]);
// an anoynomous object (also folded by default)
LogWrapper.getLog().error({a:"this is a", b:"and this is b"});
// an object with a toString method (folded by default with the name from the toString method shown)...
var testObj:Foo = new Foo("bar", 2000, ["A", "B", "C", "DDDDDD"]);
LogWrapper.getLog().warn(testObj);
// and using TRACE - add "-trace com.kelvinluck.util.LogWrapper.mtascTrace" to the commandline and compile using MTASC
trace("Check out the class and line number info above!");
trace({error:"something is wrong"}, LogWrapper.ERROR);

Into this (showing the horizontal scrollbar and four objects – three in their default folded states and one which I have “unfolded”):

SOS in action

Download:
If you want to play with this code please feel free to download it here.

The zip file includes an example class with a static main method that can be used to test the installation.

Requirements:
To use these classes successfully you will need a couple of things:

Powerflasher SOS – install it and run it. Select “Console > Bring to Front > During Connection” from the menu.

LuminicBox logger – install the extension. I have also included the LuminicBox classes in my zip just because I had to make a few tiny changes to get them to play nice with MTASC which is stricter than the MMC.

Unfortunately SOS only runs on windows so at least the machine that you view the logs on must be a windows machine.

Usage:
You can use it pretty much as demonstrated by the code above… Also check in the comments to the SOSLogPublisher code to see the arguments you can pass to the constructor etc… Just make sure that the relevant files are in your classpath.

If you are using these files along with MTASC’s trace functionality then you will need to add the following argument to your command line to MTASC:

-trace com.kelvinluck.util.LogWrapper.mtascTrace

Also note that you can do the following to toggle the display of line numbers:

LogWrapper.logCallingFile = false; // stops class / file / line number information from being logged
LogWrapper.logCallingFile = true; // starts class / file / line number information getting logged.

If you are using in with FDT then I found it really useful to create 4 Templates (“Window > Preferences > FDT > Templates > Templates”) to speed up entering debug code. For example, I have the “ze” (without the quotes) mapped to:

LogWrapper.getLog().error(${cursor});

or if you are using MTASC and trace you may find this more useful:

trace(${cursor}, LogWrapper.ERROR);

And similarly for the other log levels.

When you publish your finished file for production you can simply comment out the relevant addPublisher line to you don’t need to worry about the innards of your program being visible to others. Or if you use MTASC and trace then even better – just change the trace argument to be “-trace no” and the traces won’t even be compiled into your final swf!

History:
2005-08-30: First release
2005-08-31: Added folding so that Objects, Arrays etc which are logged are folded by default and can be opened inside SOS if you desire.
2005-09-01: Bug fix so XML and other objects containing < and > can be debugged.
2005-09-01: Added support for MTASC’s TRACE command
2005-09-10: Wrapped traced data in CDATA block so that ampersands, < ‘sand >’s can be successfully debugged.
2005-09-14: Added Natural Docs to the download zip.

Feedback:
If you find this useful or can think of a way that it can be more useful then please let me know below by leaving a comment below or via the OSFlash mailing list.



Source for tag complete in del.icio.us example


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

When I released my tool for Tag autocompletion when posting to del.icio.us it didn’t occur to me that people might not be comfortable posting their username and password to my server. But thinking about it this is a reasonable concern. Rather than just ask you to take my word for it that I’m not harvesting usernames and passwords I decided to release the source for the app and make it so that you can host it on your own server…

First up, the PHP. When you log into the app it connects to a PHP script for two reasons:

  • It checks that del.icio.us is contactable and that your login details are correct.
  • It retrieves a list of your tags for use in the autocomplete

This script is called get_tags.php and looks like this:

/**
 * get_tags.php
 *
 * Connects to the del.icio.us server and retrieves a list of tags for a given user
 * and returns this or a sensible error code.
 *
 * Kelvin Luck < kelvin at kelvinluck dot com >
 * This code is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike License
 * <http ://creativecommons.org/licenses/by-nc-sa/2.0/>
 */


define("_URL_GET_TAGS", "http://del.icio.us/api/tags/get?");

$username = isset($_POST["username"]) ? $_POST["username"] : "nobody";
$password = isset($_POST["password"]) ? $_POST["password"] : "thisiswrong";

$cu = curl_init(_URL_GET_TAGS);
curl_setopt($cu, CURLOPT_RETURNTRANSFER, true);
curl_setopt($cu, CURLOPT_USERPWD, "$username:$password");
curl_setopt($cu, CURLOPT_USERAGENT, "kelvinluck.com del.icio.us poster (PHP-" . phpversion() . ")");

$method_result = curl_exec($cu);
$result_info = curl_getinfo($cu);
curl_close($cu);

switch ($result_info["http_code"]) {
    case 200:
        $p = strpos($method_result, "< ", 1);
        $content = substr($method_result, 0, $p)."<result status=\"1\">\n".substr($method_result, $p)."";
        break;
    case 401:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP401 - Bad login details.\" />";
        break;
    case 503:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP503 -  Gateway timeout.\" />";
        break;
    default:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP".$result_info["http_code"]." -  Unknown.\" />";
        trigger_error("I don't know what to do with http code:".$result_info["http_code"], E_USER_ERROR);
        break;
}

header("Content-Type: text/xml");
echo $content;

Then when you actually post your page the app connects to another PHP script called post_page.php which looks like this:

/**
 * post_page.php
 *
 * Connects to the del.icio.us server and posts a given page to the given
 * user's account.
 *
 * Kelvin Luck < kelvin at kelvinluck dot com >
 * This code is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike License
 * <http ://creativecommons.org/licenses/by-nc-sa/2.0/>
 */


define("_URL_POST", "http://del.icio.us/api/posts/add?");

$username = isset($_POST["username"]) ? $_POST["username"] : "nobody";
$password = isset($_POST["password"]) ? $_POST["password"] : "thisiswrong";

$post_data = array();
$post_data["url"] = isset($_POST["url"]) ? $_POST["url"] : "";
$post_data["description"] = isset($_POST["description"]) ? $_POST["description"] : "";
$post_data["extended"] = isset($_POST["extended"]) ? $_POST["extended"] : "";
$post_data["tags"] = isset($_POST["tags"]) ? $_POST["tags"] : "";

$ch = curl_init(_URL_POST);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_USERAGENT, "kelvinluck.com del.icio.us poster (PHP-" . phpversion() . ")");
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$method_result = curl_exec($ch);
$result_info = curl_getinfo($ch);
curl_close($ch);

switch ($result_info["http_code"]) {
    case 200:
        $p = strpos($method_result, "< ", 1);
        $content = substr($method_result, 0, $p)."<result status=\"1\">\n".substr($method_result, $p)."";
        break;
    case 401:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP401 - Bad login details.\" />";
        break;
    case 503:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP503 -  Gateway timeout.\" />";
        break;
    default:
        $content = "< ?xml version='1.0' standalone='yes'?><result status=\"0\" message=\"HTTP".$result_info["http_code"]." -  Unknown.\" />";
        trigger_error("I don't know what to do with http code:".$result_info["http_code"], E_USER_ERROR);
        break;
}

header("Content-Type: text/xml");
echo $content;

As you can see, the scripts use the CURL libraries to connect to the del.icio.us server with the relevant access details. They then return some valid XML to the calling flash file.

Now for the Flash file… Since I used components from a commercial library (the BJC Bit Component Set) I can’t actually post the fla file. But you can download the swf in the zip file below and can use it from your own server.

It also accepts an additional parameter: serverPath via FlashVars. This tells it which server to connect to.

If you leave this parameter out or set it to “” then the app will look in the same directory as itself for the get_tags.php and post_page.php pages. If you provide a value for this parameter then the app will prepend this path to it’s requests for the php files.

Here are the files for you to download:

kelvinluck_delicious_poster.zip

You will need one more thing to make the files in the zip work. I use Geoff Stern’s standards compliant JavaScript method of embedding Flash in the page. You will need to download the flashobject.js file from him and place it in /includes/flashobject.js on your web server or else edit line 15 of post.php.

And of course you will need to edit the bookmarklet to point to your server rather than mine.

Good luck and please leave comments with any ideas for improvement or bugs you find.



Simulating access to the Flickr API offline


Update:

This is a very old page imported from my previous blog. If there is missing content below or anything that doesn’t make sense then please check the page on my old blog.

As I recently mentioned on the Flickr API mailing list, I’m just about to head off on a road trip where I will have my laptop and maybe a little spare time but no Internet connection. And I was thinking it would be good to carry on putting together my Flickr API library for AS2. But – to test my methods requires an internet connection to connect to the Flickr servers… Or does it?

I wrote the script below to use as a proxy between Flash and Flickr. You will need to use a proxy of some kind anyway as you cannot connect directly from a Flash movie on one domain to servers on another domain (due to the cross domain security sandbox in the Flash Player). This proxy saves all the XML it receives from Flickr as files on your file system. Then if it can’t reach the Flickr servers (e.g. when you are offline) it will serve up the contents of these files instead. Well – it will if you make a call to the API which is identical to one which has been cached. Otherwise it will serve up a custom error document which is similar to the standard Flickr error document except it contains a unique error code so you can deal with it in your application.

Without further ado, here is the code:

// CONFIGURATION
define("_SAVE_PATH", dirname(__FILE__)."/downloaded/");
define("_ERROR_MESSAGE", "< ?xml version=\"1.0\" encoding=\"utf-8\" ?><rsp stat=\"fail\"><err code=\"999\" msg=\"No access to flickr.com and no cached result for this request\" /></rsp>");
define("REST_ENDPOINT", "http://www.flickr.com/services/rest/");

if ($fp = @fopen("http://flickr.com", "r")) {
    // we are online and can see flickr.com - cache any requests that come in
    fclose($fp);
    define("_ONLINE", 1);
} else {
    // we can't see flickr.com - try an serve up cached pages instead...
    define("_ONLINE", 0);
}

$querystring = array_pop(explode("?", $_SERVER["REQUEST_URI"]));
$query_parts = array();
parse_str($querystring, $query_parts);
ksort($query_parts);

$filename = "";
foreach($query_parts as $key=>$value) {
    $filename .= $key."|".$value."||";
}

$filename = urlencode($filename);

if (_ONLINE) { // we are online - go get the info and save it!
    $fp = fopen(_SAVE_PATH.$filename, "w");
    $content = file_get_contents(REST_ENDPOINT."?".$querystring);
    fwrite($fp, $content);
    fclose($fp);
} else { // we are offline - serve up the saved file if it exists or the error message if you can't find a file
    if (file_exists(_SAVE_PATH.$filename)) {
        $content = file_get_contents(_SAVE_PATH.$filename);
    } else {
        $content = _ERROR_MESSAGE;
    }
}
header("Content-Type: text/xml");
echo $content;

As you can see, it’s pretty simple really. It’s not tested all that comprehensively yet but it should work. The next thing to do is to write a script which automatically calls this script once for each possible outcome of each call to the Flickr API so that you have a complete library of the possible returns on your machine.

One side note… I wouldn’t recommend running this script on a public webserver. It doesn’t feel too secure to me to be writing files which are named based on the querystring to your webserver. At least change the _SAVE_PATH so that people don’t know where to look for the files. Probably a bit paranoid but better safe than sorry, ey? And anyway – you should be running the it on your laptop anyway so this shouldn’t be an issue :)

If you want to download the complete script with commenting etc then please do: flickr_cache.phps. Any comments, ways to improve it or ideas on how to easily implement the “call every method once” script please use the form below…