This time, it's personal.

I admit to having more than a soft spot for Adobe’s technology, but that’s not why I was so peeved when Steve Jobs made his much ballyhooed announcement that Flash on mobile was just the worst thing since Hitler.

For starters, much of Jobs’ characterization was just plain wrong. His problems with Flash not being “open” were gloriously misinformed as Adobe’s open source efforts, which included Flash and Flex, plus many of the video, audio, and networking technologies they contain, were already well established as open source technologies at that point.

If the argument is that the Flash virtual machine isn’t open source, one can easily turn that around and correctly state that Safari, the virtual machine for HTML5 / JavaScript, is also not open source. To say that Apple has been a champion of giving people choice is not exactly true, so claiming that they’re championing HTML5 “for the people” is equally as dubious. In fact, HTML5 itself is not as open as some may claim.

Jobs’ insistence that HTML5 is a “standard” was equally incorrect, as the myriad of sites, services, and projects that have sprung up trying to either work around or at least document the many incompatibilities, quirks, and just old plain lack of support in the HTML5 “standard” attest to. This lack of standardization is so pronounced that it alone can be used to successfully identify individual browsers and de-anonymize users (perhaps even much worse).

Jobs didn’t think Flash was needed to access video since HTML5 formats like H.264 (MP4) were so ubiquitous. On this point I’d have to agree, but that was never the sole reason that Flash became synonymous with online video. The reason Adobe’s player won out was because of a robust server architecture that, coupled with Adobe’s edge Content Delivery Networks, makes the technology a viable solution. Delivering MP4 streams over HTTP to browsers from a server farm is a lot more prohibitive, with cost being just one factor.

In his Flash critique, Jobs made the unsubstantiated claim that Flash was the number one reason why Macs crashed, a statement he never bothered to back up and which, as far as I know, was purely fictional or at best incredibly scape-goaty.

While Flash wasn’t a great mobile experience, mostly because it wasn’t set up to both deal with mobile gestures and co-exist on a web page, the same could be said of unresponsive or badly designed HTML5 sites. And whatever shortcomings the Flash player had, I’m sure they could have been addressed.

I’m not the only person to think that Flash was done away with unjustly. Amazon is trying to revive it for their devices, BlackBerry never lost it, and it Android can’t quite seem to shake itself of it. Judging by the popularity of the “How do I manually install Flash on my mobile device?” topic over at Adobe — 24.7 million views as of today– Flash is far from fading away.

The biggest injustice in this debacle, however, is the one I see being imposed on web developers.

As someone who has worked in ActionScript 3 for years, going to HTML5 / CSS3 / JavaScript was like taking a step ten years into the past. The same issues we were dealing with in Flash back then — performance, load times, code organization — are all back with a vengeance.

Even something as basic as frame-based animation is only partially supported in most modern HTML5-capable browsers with most requiring reams of code and workarounds for “full compatibility”.

Rudimentary sound support, along with the same issues we were grappling with in Flash many years ago (synchronization, maximum mixing channels, etc.), has made it’s way into HTML5. Instantly, developers learned that the web “standard” wasn’t quite up living up to its name.

Companies too have learned that they prematurely jumped on the HTML5 bandwagon and have had to re-think their approaches.

But it’s not that HTML5 is terrible.

In fact, as devices get faster and capable of handling more complex code, the mobile browser will slowly evolve to become it’s own robust virtual machine, much as Java and the Flash player are now. Eventually, much of what is today erroneously called a “standard” will actually become one, mostly, give or take, across most browsers. But we’re not even close to getting there yet.

Still, now that I’ve had time to truly immerse myself in HTML5 and experience how it works across browsers and devices, I see the potential for a much improved web experience.

As I mentioned, HTML5 to me seems like Flash circa 2003, around version 7 , with the additional complexity of browser fragmentation. Under the hood, there isn’t much that close-to-metal developers have at their disposal. Execution speed, JavaScript functionality, even things like error handling, are up to the whims of the browser manufacturers. jQuery definitely helps a lot, but cross-browser compatibility requires that you don’t push things to the bleeding edge.

Up front, the only real improvements are in the tools available for expression — better ways to animate, addition of sound and audio, improved font handling, dynamic drawing APIs, and so on. This can all be put together to produce nice, expressive web sites and games, examples of which are more quite numerous these days. I’ve even done a couple myself. And it’s because of this exposure that I can’t recommend HTML5 for anything other than nice, expressive sites and somewhat basic interactive games. The so-called “standards” required for more demanding code are simply not up to snuff and are more often a hindrance than a help.

The fact that HTML, regardless of version, is still the de facto markup language, and that JavaScript is still the de facto functional language, means that they simply aren’t likely to go away any time soon. That’s not necessarily a bad thing, but we really should be checking our (often deluded) exuberance about the browser technologies at the door.

August 14th, 2014

Posted In: Development / Coding, Internet / Web

Tags: , , , , , , ,

One Comment

I want to jump right into this topic because it’s neat and interesting and fun, but there are a couple of things we need to set up first.


But I promise it’s not going to kill you.

Step 1 of 3 — get yourself a RTMFP rendezvous server. Absolute must unless you’re only communicating over a LAN or Wireless LAN (then it just works by magic).

Some options:

  1. If you prefer free and in control, grab the open source Windows command-line version of OpenRTMFP/Cumulus
  2. If you’re feeling adventurous, you can also compile the C++ source code for your own setup:
  3. If you’re not willing to muck around with a command prompt or a compiler, sign up for a free Cirrus / account at:
  4. If you’re able and willing to plunk down a cool wad, go with Adobe’s Media thingie:

Step 2 of 3 — grab a copy of SwAG and have a quick look at how SwAG events work; the P2P stuff uses these kinds of events to communicate, so it’ll come in handy.

Step 3 of 3 – now the good stuff — write your application!

SwAG’s most important class is I chose the name because it sounds nice and fluffy and amorphous. A SwagCloud is an enhanced, self-managing NetGroup, if you want to get technical (but you don’t need to).

Creating a SwagCloud is as simple as creating an instance, adding a listener to it, and then launching the new connection on its way.

Here’s a shortened version of how that might look in real life…first we declare some variables (or you can always hard-code these if you like):

private var _RTMFPServerAddress:String="rtmfp://"; //Or rtmfp:// if running Cumulus locally
private var _developerKey:String="SOMEVALUE"; //Get this via the Cirrus sign-up process; ignore (or set to empty) if using Cumulus
private var _group:SwagCloud=null;
private var _groupName:String="MyTotallyUniqueGroupName123";
private var _groupPassword:String="Members0nly!";
private var _groupHash:String="E><tr4$3curity";

…then we set up a few useful listeners and start up our group:

SwagDispatcher.addEventListener(SwagCloudEvent.GROUPCONNECT, this.onConnect, this, this._group);
SwagDispatcher.addEventListener(SwagCloudEvent.BROADCAST, this.onBroadcast, this, this._group);
SwagDispatcher.addEventListener(SwagCloudEvent.DIRECT, this.onDirectMessage, this, this._group);
this._group=new SwagCloud(this._RTMFPServerAddress, this._developerKey);
this._group.connectGroup(this._groupName, true, this._groupPassword, this._groupHash, false);

…and then we define the listeners to do some stuff we want:

public function onConnect(eventObj:SwagCloudEvent):void {
 trace ("We're connected to the group.");
public function onBroadcast(eventObj:SwagCloudEvent):void {
 trace (eventObj.remotePeerId+" broadcast this to the group: ";
public function onDirectMessage(eventObj:SwagCloudEvent):void {
 trace (eventObj.remotePeerId+" sent this directly to me: ";

…and that’s it.

As I mentioned earlier, if you wanted to hard-code variables right into the functions, you could shorten this example by a third. And if you only wanted to create a “beacon” type of app that didn’t need to listen to broadcasts or direct messages, you could further cut the example by another two-thirds.

If you store peer IDs when they connect to the group (another event you can listen to), sending an encrypted message directly to a peer is as easy as:

var someData:Object=new Object();
someData.message="Hello there!";
this._group.send(someData, peerID);

…and broadcasting to all peers even easier:

var someData:Object=new Object();
someData.message="Hello there!";

You don’t need to create an object for the data, I just do that for extensibility. The following is also perfectly valid:

var someData:String="Hello there!";

…or even:

this._group.broadcast("Hello there!");

“But hang on, Patrick,” you say astutely,” isn’t this just an overlay on top of the built-in RTMFP networking?”

Yup, that’s pretty much exactly what it is. It doesn’t do anything that RTMFP don’t does, it just simplifies things a lot.

Just have a look at the guts of SwagCloud to see what I mean. A from-scratch NetGroup connection, for example, is a two-step process; this is handled in SwAG and simplified to one step. Even something as seemingly trivial as streaming peer-to-peer audio via a NetStream.publish call is likely to encounter a microphone security dialog — handled by SwAG. Plus, SwagCloud takes care of converting native data types for transmission (I’ve even successfully transferred prototype MovieClip instances extracted from running code, believe it or not), so you may get some useful info out of it even if you’re already RTMFP-experienced.

In other words, if you haven’t worked much with RTMFP and/or want to skip the hassle of getting it up and running (i.e. you’d rather focus on doing something with it), I’d recommend having a look at SwAG’s SwagCloud class as well as its supporting classes like SwagCloudData, SwagCloudShare, and SwagCloudEvent.

So what exactly can you do with RTMFP and, by extension, with SwAG?

  • Any type of low-latency, peer-to-peer chat or gaming application one could care to imagine.
  • A one-to-many video or audio (or both) broadcasting system. The RTMFP server only takes care of connecting peers to each other, the streams are then distributed and multiplied peer to peer. If you host the RTMFP server on the same computer or device, you can stream to many thousands directly — no third-party needed. Read more about how this works in SocialCastr.
  • A distributed file sharing service. Despite the contrary claims that Adobe makes on the subject, SwAG has an entire class that proves that this is very possible. Adobe doesn’t make it super easy to use though, hence SwAG.
  • A distributed computing platform. Distributing a module designed to run concurrent calculations (Bitcoin mining, for example), is simply a matter of sharing the “workhorse” SWF with your peer group (SwagCloud instance), and having them run it with some custom startup parameters. Or that module could be part of a more static, custom-built application; that’s a perfectly valid approach too, albeit not as flexible in the long run.
  • An overlay network. If enough peers participate in it, many of the above features could be combined into a very robust overlay network that could have many of the features of something like Tor. Or BitTorrent. Or Hulu. Or maybe with the assistance of dedicated peers, a new type of content delivery network.

The (personally tested) possibilities of RTMFP are tough to shrug off, but at the same time they seem somewhat secondary to Adobe who seem content to leave some of the detailed implementation questions to developers. For example, object replication (peer-to-peer data sharing) is still somewhat of a black art, and even something as basic as a NetGroup connection event is not well-advertised as actually coming from the NetConnection object. So as I mentioned earlier, even if you don’t end up using SwagCloud, hopefully you’ll be able to use what I learned in what was often good old-fashioned trial-and-error to build your exciting new p2p products.

August 13th, 2014

Posted In: Development / Coding

Tags: , , , , , ,

Leave a Comment

First of all, SwAG isn’t for everything.

There are many things that Adobe and other excellent developers have done that I don’t think needs any improving.

Standard events, for example, are pretty rock-solid:

this.addEventListener (Event.ENTER_FRAME,  this.frameLoop);

And event listeners are easy to set up:

private function frameLoop (eventObj:Event):void {
   trace ("Another frame has just elapsed...");

It works great … why mess with it?

But there are some obvious limitations.

First of all, you need to attach, or couple, your event to something. In the above example, it’s “this”, which would be the equivalent to:

addEventListener (Event.ENTER_FRAME,  frameLoop);

The default is “this” unless otherwise specified, so a source is always specified.

In SwAG, the source is optional. Listeners are created through the swag.core.SwagDispatcher class:

SwagDispatcher.addEventListener(SwagEvent.SOME_EVENT, this.eventResponder, this);

There’s an extra parameter at the end where you specify the “this” object in which the event listener will run (that’s not what you’re listening to!)

I usually add it, and 99% of the time it’s “this”, but I think it’s optional anyway.

SwAG listeners look pretty much the same:

public function eventResponder(eventObj:SwagEvent):void {
   trace ("I got that event!");


SwAG event listeners MUST BE PUBLIC!

For example, the following function would never be called when the associated event fires:

private function eventResponder(eventObj:SwagEvent):void {
   trace ("I will never get that event!");

Nothing will crash terribly if you do this, I should mention, it just won’t work.

Dispatching an event can be done from anywhere with relative ease:

var newEvent:SwagEvent=new SwagEvent(SwagEvent.SOME_EVENT);
SwagDispatcher.dispatchEvent(newEvent, this);

One upshot with SwAG here is the fact that you don’t need to sub-class anything (like the EventDispatcher class, for example).

If you want to make things more interesting, there’s nothing holding you back from extending EventDispatcher either.

And that’s what decoupled, or source-optional means. The listeners are not attached to, tied to, associated with, coupled to, or otherwise source-specific.

But they can be, with an extra optional parameter:

var sourceObject:YourOwnClass=new YourOwnClass();
SwagDispatcher.addEventListener(SwagEvent.SOME_EVENT, this.eventResponder, this, sourceObject);

Your own class can dispatch events into the æther and any part of the same application context can listen for them.

In practical terms that means one event listener for any number of event broadcasters, unlike the standard ActionScript one-to-one ratio. Here’s what I mean…standard ActionScript 3:

public function eventListener(eventObj:Event):void {
for (var count:uint=0; count<1000; count++) {
   var newObj:YourOwnClass=new YourOwnClass();
   newObj.addEventListener(Event.ENTER_FRAME, this.eventListener);

…and the same in SwAG:

public function eventListener(eventObj:SwagEvent):void {
SwagDispatcher.addEventListener(SwagEvent.SOME_EVENT, this.eventListener, this);
for (var count:uint=0; count<1000; count++) {
   var newObj:YourOwnClass=new YourOwnClass();

Subtle, but I find it useful.

And you can create listeners for events for objects that don’t exist yet. I also find that useful.

Other subtleties can be found in the SwagDispatcher class and the few helper classes that accompany it. Other parts of SwAG make use of this event system, so it’s worth a look.


February 5th, 2014

Posted In: Development / Coding

Tags: , , , , ,

Leave a Comment

Yeah,  application frameworks are a dime a dozen these days. That’s why SwAG isn’t a framework but rather a toolkit — the Swiss Army Gear toolkit for ActionScript 3, to be exact.

As the name implies, this small-ish library contains useful code that can be employed wherever needed, as needed, without the full weight or presumptuousness of a full-on framework. In other words, it should be useful for you whether you have a project that’s well under way or are just starting out.

Because the library has lots of disparate features, I’ll be dedicating a number of future posts discussing them in detail and providing (hopefully) useful examples. Here is a brief preview:

  • Peer-to-peer networking, streaming, data sharing using RTMFP (via and
  • Decoupled or source-optional event broadcasting (via swag.core.SwagDispatcher)
  • Extended MovieClip playback control (via swag.core.instances.SwagMovieClip)
  • SQLite database functionality for Adobe AIR (via swag.core.instances.SQLiteDatabase)
  • Extended date/time manipulation and comparison functionality (via swag.core.instances.SwagDate and swag.core.instances.SwagTime)
  • Extensive data analysis/conversion functionality (via swag.core.SwagDataTools)
  • ZIP data access and extraction (via swag.core.instances.SwagZip)
  • Extended runtime introspection (via swag.core.SwagSystem)
  • Extended HTTP/HTTPS data loading functionality (via swag.core.instances.SwagLoader)

Some of these are used extensively in SocialCastr and Araknid and they definitely made my life easier. SwAG doesn’t presume to be the only code running so adding it or removing it, mid-project or otherwise, is easy and straightforward. I’ve chosen to not include it in pure AS3 projects like TorAS or WRASE because they’re much more useful as autonomous libraries. Applications, however, are meant to be used rather than incorporated into other projects, so in those cases I happily stick SwAG into every nook and cranny.

SwAG had additional functionality which has been yanked since these make way more sense as individual projects:

  • BMPImage, for loading and displaying Windows Bitmap (BMP) images
  • RSSChannel, for reading and manipulating RSS feed (XML) data
  • XLSXFile, for reading and manipulating a Microsoft Office Open XML Format Spreadsheet (Excel) file

I don’t know if / when I’ll get around to updating and publishing these but I’m not averse to sharing if you want to get in touch with me directly.

Otherwise, grab yourself a copy of the pretty-well-documented toolkit here:
…and stay tuned for SwAG fun and frolic.

January 29th, 2014

Posted In: Development / Coding, Internet / Web

Tags: , , , , , , , ,


They said it couldn’t be done.
They called me insane.

Okay, no one actually called me insane, but that’s besides the point because it is definitely possible to edit the Windows Registry using Adobe AIR with help from “regedit.exe“.

When I say edit, I mean the whole shebang — read, update, create, and delete. If that sounds useful, I suggest reading on…

Installation / Setup

  1. Grab the WRASE source code:
  2. Set up your development environment. Anything that produces AIR applications will do but be aware that you must enable only the “extendedDesktop” profile in the application descriptor file (application.xml), before trying to run the code:

    In most IDEs you can also accomplish this simply by enabling the “Extended Desktop” profile, otherwise you’re liable to see this when you try to execute the code:

    Error #3219: The NativeProcess could not be started. 'Not supported in current profile.'

You should now be able to execute the “Main” class to see some of the functionality of WRASE. Note that Windows will display the UAC security dialog box whenever you try to interact with “regedit.exe” using WRASE. While this is standard behaviour, it shouldn’t be necessary in the next update, but in the meantime you can prevent the constant dialogs by running your AIR application (or IDE) with Administrator privileges (right-click on the program icon -> Properties -> Compatibility -> Run this program as an administrator).

WRASE has two main components: the registry loader / updater (WindowsRegistryEditor), and registry data parsers (model.WindowsRegistryKey, and model.WindowsRegistryEntry).

Most of the time you will probably only interact with the WindowsRegistryEditor — it will generate instances of WindowsRegistryKey and WindowsRegistryEntry from loaded registry information.

Each WindowsRegistryKey instance contains the information for that key: its path (“HKEY_CURRENT_USER\Control Panel\Desktop\Colors“, for example), and the data or entries found immediately in that key (for example, ActiveBorder, ActiveTitle, AppWorkSpace, etc.)

Here’s what that looks like in Windows Registry Editor — keys on the left, entries (data) on the right: WindowsRegistrySample

Each WindowsRegistryKey can have any number of WindowsRegistryEntry instances, as well as child WindowsRegistryKey instances. for example, in the window above “HKEY_CURRENT_USER\Control Panel\Desktop” has 4 child keys (Colors, LanguageConfiguration, MuiCached, and WindowMetrics).

WRASE attempts to be as self-managing as possible. That means dynamically converting between the registry datatypes (“REG_SZ”, for example), and native ActionScript data types, creating, managing, and deleting .cmd and .reg files when interacting with Windows Registry Editor, and just generally staying out of your way.

The code is well documented but some accompanying documentation might come in handy. Nevertheless, it’s not a huge library, most public methods are pretty self-explanatory, and the included Main class demonstrates almost all of the functionality.

I know of a number of fixes, functional updates, and optimizations that need to be made, so I highly recommend either bookmarking the code repository or grabbing a SVN client and maintaining a synchronized local copy.

In the meantime, I welcome your questions, comments, and concerns.

January 24th, 2014

Posted In: Development / Coding

Tags: , , ,

Leave a Comment

I imagine that this application would benefit from a little TorAS integration but in the meantime it’s still pretty useful [downloads are at the bottom of this post].

Since I spent time figuring out how to describe it on Google Code, I thought I’d rather post that than hurt my brain again. So here’s what Araknid does:

[Araknid is] An Adobe AIR application that crawls and archives web pages and assets such as images and scripts into a searchable/convertible SQLite database file. Use it to create a deep search index of your (or any other) site, your intranet, or even the whole web. Uses user-defined regular expressions to match and extrapolate links and other dynamic site information. Built on SwAG Toolkit ActionScript 3 application framework, user interface developed in Flash CS6 (.fla file). Dynamically supports latest versions of Adobe AIR on mobile.

Araknid is still quite new, meaning database interactions will need to be optimized, regular expressions will need to be tweaked, and some important features will need to be added (for example, crawl only under a specific URL or domain). But core functionality should be there, along with plenty of room for growth for anyone who wants to fiddle with the source code.

Here’s what you’ll see when you run the software…the legend is below:

Araknid User Interface Legend

1. Click here to select where the Araknis database (SQLite) will be stored. Once set, clicking here will open the SQLLite database file in the default application.
2. Pauses and unpauses any currently running crawl.
3. Opens the currently running crawl URL (#6) in a new browser window (if you’re curious to see what’s being crawled).
4. Variable crawl delay (in seconds). Prevents servers from flagging Araknid as an attacker or malware.
5. The percentage loaded of the currently running crawl URL (#6).
6. The currently running crawl URL. Pause Araknid, enter your own URL, and unpause to start a new crawl.
7. Countdown clock showing when the next crawl will happen (in seconds). Based on the crawl delay (#4).
8. A list of URLs extracted from the currently running crawl URL. These are added to the database (if not already crawled), and queued for subsequent crawls.

TIP: Rename or delete the database file to reset Araknid (like a new install).

ADVANCED TIP: You can update the regular expressions used to filter / extract URLs from various HTML tags. To do this:
1. Shut down Araknid.
2. Update the configuration XML file (config.xml) in the Araknid installation directory (for example, C:\Program Files (x86)\Araknid\config.xml).
3. Delete the Araknid application storage folder: C:\Users\[YOUR USER NAME]\AppData\Roaming\Araknid\Local Store
 WARNING: This step resets your application settings (you’ll need to re-select the database file on next startup if you want to continue previous crawls).
4. On next restart, Araknid will use the new regular expressions for subsequent crawls.

Araknid v1.0 Downloads

All desktop operating systems:

  1. Download and execute Adobe AIR installer:
  2. Download and install Araknid:

Windows (32/64-bit):

Source code (ActionScript / Flash CS6 user interface):

…and something to view the resulting SQLite database:

January 21st, 2014

Posted In: Development / Coding, Internet / Web

Tags: , , , ,

Leave a Comment

This TorAS thing is going to be a lengthy project — there’s still lots left to implement and older code to one day upgrade — but I received some great feedback about the appropriateness of communicating over Tor using unencrypted HTTP (it’s not very appropriate in many situations). So I’ve branched the current stable version and added TLS/SSL support (courtesy of as3crypto), in a development branch:

I’ll add it back into the main branch as soon as I get some time to document it fully, but the bulk of the new functionality has been covered in the updated Developers Guide:

I may also have added source code comments so have a peek.

TorAS ActionScript source code is made public under a liberal license (MIT), developed on open source technology (FlashDevelop), incorporating open source technology (Tor “Expert Bundle” binary , released under a similar license and spirit),  and comes with a handy step-by-step guide. It’s most powerful on Windows (on account of the bundled Tor binary), but you should be able to use it on AIR for Android with something like Orbot (is there an official Tor router app for iOS?), and in fact anywhere AIR and Tor can run together.

January 21st, 2014

Posted In: Development / Coding, Internet / Web, Privacy / Surveillance

Tags: , , , , , , ,


A revealing breakdown of known Tor Exit Node locations by


January 14th, 2014

Posted In: Internet / Web, Privacy / Surveillance

Tags: , ,

Leave a Comment

The following is a sample usage of the FileFinder class I developed for an ongoing project. You’ll find the source code below the usage samples. It’s pretty generic and has only one purpose — to recursively search (through subdirectories) for a specific file name from a starting directory. Usage is straightforward…

package {

	import FileFinder;

	public class Main extends Sprite {

		private var _fileFinder:FileFinder = null;

		public function Main() {
			//Find "chrome.exe" somewhere on the c: drive...
			this._fileFinder = new FileFinder("c:\", "chrome.exe");

Additionally, you can create an optional list of excluded directories, as a File vector array, that you don’t wan’t searched:

package {

	import FileFinder;
	import flash.filesystem.File;

	public class Main extends Sprite {

		private var _fileFinder:FileFinder = null;

		public function Main() {
			//Find "chrome.exe" somewhere on the c: drive, but don't look in "c:\Windows\"...
			var exclusions:Vector.<File>=new Vector.<File>();
			this._fileFinder = new FileFinder("c:\", "chrome.exe", exclusions);

I’ve fully commented the source code below, as well as sections of the class that I highly recommend you update. At the very least, update the section that says “File was found! Do something with currentFile here…” (because it’s not a very useful class otherwise :) )

//Update package path as desired...
package  {

	import flash.filesystem.File;

	public class FileFinder {

		private var _basePath:* = null;
		private var _fileName:String = null;
		private var _currentDir:File = null;
		private var _directoryStack:Vector.<File> = null;
		private var _completedStack:Vector.<File> = null;

		 * Searches for a specific file from a specified base path, optionally excluding certain paths.
		 * @param	basePath The base path (for example, "c:\" or "/") to begin the search at.
		 * @param	fileName The file name ("file.ext") to search for.
		 * @param	exclusions An optional File vector array of paths to exclude. Any File items that are not
		 * directories will be removed.
		public function FileFinder(basePath:*, fileName:String, exclusions:Vector.<File>=null) {
			this._basePath = basePath;
			this._fileName = fileName;
			if (exclusions != null) {
				//The exclusions are the completed stack, we just need to ensure that they're all directories.
				this._completedStack = exclusions;
				this._completedStack = this.pruneNonDirectories(this._completedStack);
			} else {
				this._completedStack = new Vector.<File>();
			this._directoryStack = new Vector.<File>();			

		 * Begins the file search by resolving the base path and starting the initial asynch file list retrieval.
		private function findFile():void {
			this._currentDir = resolveToFile(this._basePath);						
			if (this._currentDir == null) {
				trace ("Couldn't resolve root directory: \""+this._basePath+"\"");
			this._currentDir.addEventListener(FileListEvent.DIRECTORY_LISTING, this.onDirectoryListing);

		 * Event handler for asynch directory listing.
		 * @param	eventObj A FileListEvent object.
		private function onDirectoryListing(eventObj:FileListEvent):void {
			var dirString:String = new String();
			this._currentDir.removeEventListener(FileListEvent.DIRECTORY_LISTING, this.onDirectoryListing);
			//Current directory being searched: this._currentDir.nativePath
			//We set _currentDir to null as a precaution here. Not 100% necessary.
			this._currentDir = null;			
			var fileList:Array = eventObj.files;
			if (fileList.length == 0)  {
				//This is an empty directory so abort here and keep searching...
				this._currentDir = this._directoryStack.shift();
				this._currentDir.addEventListener(FileListEvent.DIRECTORY_LISTING, this.onDirectoryListing);
			//Number of directories remaining to be searched: this._directoryStack.length
			//Directories already searched: this._completedStack.length
			for (var count:uint = 0; count < fileList.length; count++) {
				var currentFile:File = fileList[count] as File;
				if (currentFile.isDirectory) {
					//Current search entry is a directory so push on stack if not already searched
					if (this._completedStack.indexOf(currentFile)<0) {
				} else {							
					if ( == this._fileName) {
						//File was found! Do something with currentFile here; maybe dispatch an event?
			if (this._directoryStack.length == 0) {
				//File was not found. We're done.
			this._currentDir = this._directoryStack.shift();
			this._currentDir.addEventListener(FileListEvent.DIRECTORY_LISTING, this.onDirectoryListing);

		 * Prunes all non-directory items from the supplied File vector array.
		 * @param	stackVector An array of File items.
		 * @return  	A copy of the input vector array with only the directory items included.
		private function pruneNonDirectories(stackVector:Vector.<File>):Vector.<File> {
			return (stackVector.filter(this.pruneNonDirFilter, this));

		 * Filter function used in conjuction with a File vector's "filter" method.
		 * @param	item The File item being analyzed.
		 * @param	index The index of the item currently being analyzed.
		 * @param	vector A reference to the File vector currently executing the "filter" method.
		 * @return True if the supplied File item is a directory, false otherwise.
		private function pruneNonDirFilter(item:File, index:int, vector:Vector.<File>):Boolean {
			if (item.isDirectory) {
				return (true);
			} else {
				return (false);

		 * Resolves a native path string (for example "c:\Windows\", or relative root paths like "\" or "/"), to an
		 * ActionScript File object.
		 * @param	nativePath The native path to convert to a File instance.
		 * @return A File instance pointing to the native path specified, or null if something went horribly wrong.
		public static function resolveToFile(nativePath:String):File {
			try {
				var returnFile:File = File.userDirectory;
				if ((nativePath == "\\") || (nativePath == "/")) {
					//AIR won't resolve slashes as roots so we do a little guessing instead...
					var rootDirs:Array = File.getRootDirectories();
					returnFile = rootDirs[0] as File;		
				} else {			
					returnFile = returnFile.resolvePath(nativePath);
				return (returnFile);
			} catch (err:*) {
				return (null);
			return (null);

	}//FileFinder class


After running this a few times I realized that it’s not as efficient as it could be, but it’s a good place to start. Everything runs asynchronously so it shouldn’t affect your application much, though in my experience there are still hiccups when accessing certain directories.

January 6th, 2014

Posted In: Development / Coding

Tags: , , ,


« Previous PageNext Page »