Apollo Local File System

From Adobe Labs

By Darron Schall (http://www.darronschall.com)

A key feature Apollo brings is the ability for web applications to interact with the local file system on the user's computer. Examples of the file system interaction exposed through Apollo include activities such as listing the contents of a directory, reading and writing text and/or binary files, copying and moving files, etc. This article will guide you through a high level overview of the Apollo File API, and cover in depth some of the common tasks you'll likely want to perform.

Table of contents <script type="text/javascript">showTocToggle("show","hide")</script> [hide]
[ edit]

Requirements

In order to make the most of this article, you need the following software and files:

Prerequisites: General knowledge of ActionScript 3.0.

[ edit]

General overview

With the new File API, Apollo applications can take advantage of an unprecedented level of access to the user's local file system. Apollo applications run in their own security sandbox and aren't confined by the limitations web developers have grown accustomed to inside of the browser. In traditional web applications, the only file system action available is selecting a file to be uploaded to a server. Apollo expands on this ability by enabling developers to perform operations such as:

  • Create Files and Directories
  • Open and Read Files
  • Write Files
  • List the contents of a Directory
  • Find the user's home or documents directory
  • Inspect File and Directory Properties

The Apollo File API centers on the new flash.filesystem package. The File class is the workhorse of the File API. Most of the operations you perform will be done through him. His supporting cast includes the characters FileStream and FileMode. Their roles are broken down in the following manner:

Table 1. File API classes (flash.filesystem.*)

ClassDescription
FileThe representation of a File or a Directory
FileModeContains constant strings that specify different ways to open a file, used in the open() and openAsync() method of File
FileStreamAn object used when reading or writing files

One thing that might be confusing at first is the apparent lack of support for Directories. There exists a File class, but no Directory counterpart. It turns out that files and directories share the same basic operations (copying, moving, deleting, etc.) and are very similar on a conceptual level. Because of this, the directory functionality has been rolled up in the File class. The isDirectory property allows you to easily determine if the object being pointed to is either a file or a directory.

[ edit]

Referencing a file or directory

Creating a reference to a file is usually done in one of two ways. The first way involves the use of one of the directory shortcuts available as static constants of the File class, coupled with the resolve() method to convert a custom string path to the actual file or subdirectory itself, like this:

/*
  Create a reference to the "example.txt" file in the "apollo" subdirectory
  of my documents directory. This resolves to C:/Documents and Settings/Darron/My Documents/apollo/example.txt on Windows.
*/
var file:File = File.documentsDirectory.resolve( "apollo/example.txt" );

// Get a reference to the desktop directory.  For me, this points to
// C:/Documents and Settings/Darron/Desktop
var dir:File = File. desktopDirectory;

The second way involves setting the nativePath property of a File instance to the specific file or directory:

// Create a new file instance first
var file:File = new File();
// Use the string path to the file to get a reference to it via nativePath.
file.nativePath = "C:/Documents and Settings/Darron/Desktop/example.txt";

Table 2. The static string constants of the File class that point to common directories

Static constantDescription
appStorageDirectoryA place to store files needed by the application, such a settings or log files.
appResourceDirectoryThe application's installation directory.
currentDirectoryThe current directory of the application when it was launched. This is useful when dealing with command-line parameters.
desktopDirectoryThe "Desktop" directory underneath the user directory.
documentsDirectoryStaring from user directory, this is the "My Documents" subdirectory on Windows, and the "Documents" subdirectory on Mac OS X.
userDirectoryThe user's home directory. This is typically C:/Documents and Settings/<username>/Desktop on Windows and /Users/<username/Desktop on Mac OS X.

Once you have a reference to a file or directory, you can start interacting with the file system. The File API provides two different approaches for working with file system data, as you'll see in the next section.

[ edit]

Synchronous vs. asynchronous

When you start exploring the File API, one of the first things you'll notice is that there are two similarly named methods to perform a single operation. For example, to copy a file or directory you use either the copyTo() method, or the copyToAsync() method. While the methods are named very similarly and they do in fact perform the same operation, they are conceptually worlds apart.

In synchronous operations, the code will wait for the method to return before moving on to the next line:

 // Copy a file to the desktop
 var file:File = File.userDirectory.resolve( "apollo/example.txt" );
 file.copyTo( File.desktopDirectory.resolve( "Copy of example.txt" ) );
 trace( "Copy complete" );  // Displays after "copyTo" finishes up and
                            // the file has been copied

In asynchronous operations, the operation is started in the background and the code execution continues on without waiting for the original operation to complete. An event is generated by the background process when it's done working, letting listeners know that the operation has completed:

 // Copy a file to the desktop
 var file:File = File.userDirectory.resolve( "apollo/example.txt" );
 file.copyToAsync( File.desktopDirectory.resolve( "Copy of example.txt" ) );
 trace( "Started copy" );  // Displays right away, before the copy can finish
 
 // Listen for the "complete" event to know when the background copy process
 // has completed
 file.addEventListener( Event.COMPLETE, completeHandler );
 
 public function completeHandler( event:Event ):void
 {
    trace( "Copy complete" ); // Displays when the copy finishes
 }

Both synchronous and asynchronous operations have their place. As you can clearly see, the synchronous method results in less overall code and is more readable and easier to write. However, the downside is that if you are performing a long running process, the application will appear "stuck" until the synchronous process finishes. While the code is waiting for the operation to complete, no other code execution can be done. This means that the display list will remain frozen in time and all animations and user interactions will appear paused.

On the other hand, asynchronous operations require more care to write. Because they kick off the operation in the background, long running processes will not impact the normal use and interaction of the application. You should use the asynchronous methods when performing time-consuming actions.

The following is a list of synchronous methods and their asynchronous counterparts:

Table 3. Synchronous and Asynchronous File API methods

Synchronous methodsAsynchronous methods
File.copyTo()File.copyToAsync()
File.deleteDirectory()File.deleteDirectoryAsync()
File.deleteFile()File.deleteFileAsync()
File.listDirectory()File.listDirectoryAsync()
File.moveTo()File.moveToAsync()
File.moveToTrash()File.moveToTrashAsync()
FileStream.open()FileStream.openAsync()
[ edit]

Reading and writing files

The FileStream class provides the necessary functionality for reading and writing files. Any time you want to interact with a file, the following steps should be performed:

 # Create a reference to the file and open it with a FileStream
 # Perform the read / write operations as necessary
 # Close the file and free valuable system resources

The first step gets translated into code similar to this:

 // Create a reference to the file on the file system
 var file:File = File.desktopDirectory.resolve( "example.txt" );
  
 // Create a stream and open the file for reading
 var stream:FileStream = new FileStream();
 stream.open( file, FileMode.READ );

Once the file is opened for reading, you can use any of the read methods on the stream from the flash.utils.IDataInput interface. In this case, the file is not writeable because it was opened in read-only mode (see: Ways to Open FileStreams). If it were writeable, the write methods could be used from the flash.utils.IDataOuput interface.

Because the example.txt file is a plain text file for sample purposes, the code below reads the entire contents of the file as a string and then display that output in the console window:

 // Read the entire contents of the file as a string
 var contents:String = stream.readUTFBytes( stream.bytesAvailable )
 
 // Displays "Hello Apollo World"
 trace( contents ); 

After the file contents have been examined the only thing left to do now is clean up and close the stream:

 // Clean up - free resources that are in use
 stream.close();

Note that in the above code samples, the synchronous open() method was used for the sake of simplicity. The openAsync() method could have been substituted in its place. The major difference would be that reading and closing the file would occur in an Event.COMPLETE event handler, as demonstrated by the following:

 // Create a stream and open the file for asynchronous reading
 var stream:FileStream = new FileStream();
 stream.openAsync( file, FileMode.READ );
 
 // Add the complete event handler to know when the file has been opened
 stream.addEventListener( Event.COMPLETE, handleComplete );
 
 private function handleComplete( event:Event ):void
 {
    // Get the stream reference back from the event object
    var stream:FileStream = event.target as FileStream;
    
    // Read the entire contents of the file as a string
    var contents:String = stream.readUTFBytes( stream.bytesAvailable )
 
    // Displays "Hello Apollo World"
    trace( contents ); 
    
    // Clean up - free resources that are in use
    stream.close();
 }
[ edit]

Ways to open FileStreams

There's more to reading files than simply reading all of the data at once as a string. The FileStream class supports 4 different modes for interacting with files. The mode of interaction is specified as the second parameter in open() or openAsync() by using one of the static string constants from the FileMode class.

Table 4. Different ways to open a FileStream

Open mode constantDescription
FileMode.READThe file is opened for read-only. The file must already exist first.
FileMode.WRITEThe file is opened for write-only. The file will be created if it does not already exist. If the file does already exist, it is overwritten.
FileMode.APPENDThe file is opened for write-only. The file will be created if it does not exist. Written data will be placed the end of the file.
FileMode.UPDATEThe file is opened for read-write. If the file doesn't exist, it will be created. Data can be written to or read from any location in the file.
[ edit]

Creating, writing, and reading a binary file

The process of writing a file is essentially the same as reading a file. You need to choose the appropriate FileMode that allows you to create a file if it doesn't exist. The following example creates a new file, writes some binary data into the file, and then closes it, and then reads the data back in:

 // Create a reference to the file on the filesystem
 var file:File = File.desktopDirectory.resolve( "apollo test.dat" );
                
 // Create a stream and open the file for asynchronous reading
 var stream:FileStream = new FileStream();
 stream.open( file, FileMode.WRITE );
        
 // Write some raw data to the file
 stream.writeBoolean( false );
 stream.writeInt( 1000 );
 stream.writeByte( 0xC );
                
 // Clean up
 stream.close();
                
 // For demo purposes, open the file in read-only mode and read the data back
 stream.open( file, FileMode.READ );
                
 trace( stream.readBoolean() ); // false
 trace( stream.readInt() ); // 1000
 trace( stream.readByte().toString( 16 ) ); // c
            
 // Clean up
 stream.close();
[ edit]

Working with directories

As mentioned previously, working with directories is essentially the same as working with files. The basic idea is to create a new File instance that references the directory path. Once we have a reference to the directory, there are a few directory specific operations to perform:

Table 5. Common directory operations

Method nameDescription
File.createDirectory()Creates the directory (and all parent directories) if it does not exist.
File.deleteDirectory()Removes the directory. Can be done asynchronously.
File.listDirectory()Lists the contents of a directory. Can be done asynchronously.
File.listRootDirectories()Lists all of the root directories of the file system.
[ edit]

Creating a new directory

The following code example will create a new directory and a corresponding subdirectory on the user's desktop.

 // Create a reference to the target directory
 var dir:File = File.desktopDirectory.resolve( "Apollo/Dir Test" );
                
 // Check to see if the directory exists.. if not, then create it
 if ( !dir.exists )
 {
    dir.createDirectory();
    trace( "Directory created." );
 }
 else
 {
    trace( "Directory already exists." );
 }
[ edit]

Creating a temporary directory

Apollo allows for the ability to create a temporary directory, rather than creating a more permanent directory on the file system. Temporary directories are useful in a number of situations, one of which is a centralized place to store files that are going to get archived as a .zip file.

// Create a temporary directory via a static File method
var tempDir:File = File.createTempDirectory();
				
// Displays C:/Documents and Settings/Darron/Local Settings/Temp/fla4.tmp
trace( tempDir.nativePath );

// ...

// Delete the temporary directory once we're done with it
tempDir.deleteDirectory();
				
// Clear the variable to prevent accidentally using it at this point
tempDir = null;

Once the temporary directory is created, use the reference to the directory as a place to store anything you might need (by creating new files or directory there, or moving/coping items there). When finished, delete the directory and set the variable to null so that you don't accidentally try to reference a directory that no longer exists.

[ edit]

Listing the contents of a directory

Through the listDirectory() and listDirectoryAsync() methods, Apollo makes it easy to iterate through the contents of a directory. In general, listing directory contents should be performed asynchronously. A directory could have any number of children, from 0 upwards into the thousands. Because this could be a potentially long operation, performing it asynchronously will ensure that your application doesn't appear to be frozen while directory contents are retrieved.

The following example shows how to get the contents within a directory, and displays various properties of each item:

// List the contents of the user's home directory
var dir:File = File.userDirectory;
dir.listDirectoryAsync();
				
// Listen for the appropriate event to handle when the listing is complete
dir.addEventListener( FileListEvent.DIRECTORY_LISTING, 
                      handleDirectoryListing );

			
private function handleDirectoryListing( event:FileListEvent ):void
{
    // Display a header
    trace( "Name/tSize/tDir?/tCreated On" );
				
    // Loop over all of the files and subdirectories, and display 
    // some relevant information about each item in the console window
    for each ( var item:File in event.files )
    {
        trace( item.name + "/t" 
             + item.size + "/t" 
             + item.isDirectory + "/t" 
             + item.creationDate );
    }
}
[ edit]

Where to go from here

This article has demonstrated basic concepts of the new Apollo File API. The File API centers on the File class in the flash.filesystem package and allows developers access to the user's local file system, exposing a new world of functionality.

The File API allows for synchronous and asynchronous operations to be performed. Synchronous operations are easier to read, but have the disadvantage of locking up the application until the process completes. For long-running processes, it's best to use the asynchronous methods to kick the process off in the background, allowing the application to run as it normally would. When the process completes, an event is generated that can be handled and acted upon accordingly.

Working with files and directories is straightforward through Apollo's File API. Pay attention to the FileMode specified when opening a file, as that dictates what actions can be take on the FileStream. Working with files is the same as working with any class that supports the flash.utils.IDataInput and IDataOutput interfaces. If you're familiar with flash.net.Socket or flash.utils.ByteArray, you'll feel right at home.

  • For specific information about the classes in the File API, see the flash.filesystem package in the Apollo ASDoc documentation.
  • For general information about working with the File API, see "Using the Apollo file APIs" in the Apollo documentation.
  • For help about specific problems or for more ideas about what to do with the File API, see the Apollo for Adobe Flex developers pocket guide, specifically Chapter 4 ("Using the File System API") and Chapter 5 ("Working with the File System").
[ edit]

About the author

Darron Schall has a BS in Computer Science from Lehigh University. Darron is an independent consultant specializing in Rich Internet Applications and Flash Platform development. He maintains a Flash Platform related blog at http://www.darronschall.com and is an active voice in the Flash and Flex communities.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值