diff --git file_example/file_example.info file_example/file_example.info
new file mode 100644
index 0000000..cdc187b
--- /dev/null
+++ file_example/file_example.info
@@ -0,0 +1,9 @@
+; $Id$
+name = File example
+description = Examples of using the Drupal File API and Stream Wrappers.
+package = Example modules
+core = 7.x
+files[] = file_example.module
+files[] = file_example_session_streams.inc
+files[] = file_example.test
+
diff --git file_example/file_example.module file_example/file_example.module
new file mode 100644
index 0000000..cc7986f
--- /dev/null
+++ file_example/file_example.module
@@ -0,0 +1,523 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Examples demonstrating the Drupal File API (and Stream Wrappers).
+ *
+ */
+
+/**
+ * The File Example module is a part of the Examples for Developers Project
+ * and provides various Drupal File API Examples. You can download and
+ * experiment with this code at the
+ * @link http://drupal.org/project/examples Examples for Developers project page. @endlink
+ *
+ * See @link http://drupal.org/node/555118 Drupal File API @endlink for handbook
+ * documentation on the File API and
+ * @link file File summary on api.drupal.org @endlink for the function summary.
+ *
+ * @defgroup file_example Examples: File Examples
+ */
+
+/**
+ * Implements hook_menu() to set up the URLs (menu entries) for the
+ * file examples.
+ */
+function file_example_menu() {
+  $items = array();
+  $items['examples/file_example'] = array(
+    'title' => 'File Example',
+    'page callback' => 'file_example_intro',
+    'access callback' => TRUE,
+    'expanded' => TRUE,
+  );
+  $items['examples/file_example/fileapi'] = array(
+    'title' => 'Use File API to read/write a file',
+    'page callback' => 'drupal_get_form',
+    'access arguments' => array('use file example'),
+    'page arguments' => array('file_example_readwrite'),
+  );
+  $items['examples/file_example/access_session'] = array(
+    'page callback' => 'file_example_session_contents',
+    'access arguments' => array('use file example'),
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+
+/**
+ * Implements hook_permission().
+ */
+function file_example_permission() {
+  return array(
+    'use file example' =>  array(
+      'title' => t('Use the examples in the File Example module'),
+    ),
+  );
+}
+
+
+/**
+ * Form builder function for the file example readwrite and directory creation
+ * example.
+ *
+ * A simple form that allows creation of a file, managed or unmanaged. It
+ * also allows reading/deleting a file and creation of a directory.
+ * @ingroup file_example
+ */
+function file_example_readwrite($form, &$form_state) {
+  if (empty($_SESSION['file_example_default_file'])) {
+    $_SESSION['file_example_default_file'] = 'public://drupal.txt';
+  }
+  $default_file = $_SESSION['file_example_default_file'];
+  if (empty($_SESSION['file_example_default_directory'])) {
+    $_SESSION['file_example_default_directory'] = 'public://directory1';
+  }
+  $default_directory = $_SESSION['file_example_default_directory'];
+
+  $form['write_file'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Write to a file'),
+  );
+  $form['write_file']['write_contents'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Enter something you would like to write to a file') . ' ' . date('m'),
+    '#default_value' => t('Put some text here or just use this text'),
+  );
+
+  $form['write_file']['destination'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $default_file,
+    '#title' => t('Optional: Enter the streamwrapper saying where it should be written'),
+    '#description' => t('This may be public://some_dir/test_file.txt or private://another_dir/some_file.txt, for example. If you include a directory, it must already exist. The default is "public://". Since this example supports session://, you can also use something like session://somefile.txt.'),
+  );
+
+  $form['write_file']['managed_submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Write managed file'),
+    '#submit' => array('file_example_managed_write_submit'),
+  );
+  $form['write_file']['unmanaged_submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Write unmanaged file'),
+    '#submit' => array('file_example_unmanaged_write_submit'),
+  );
+  $form['write_file']['unmanaged_php'] = array(
+    '#type' => 'submit',
+    '#value' => t('Unmanaged using PHP'),
+    '#submit' => array('file_example_unmanaged_php_submit'),
+  );
+
+  $form['fileops'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Read from a file'),
+  );
+  $form['fileops']['fileops_file'] = array(
+    '#type' => 'textfield',
+    '#default_value' => $default_file,
+    '#title' => t('Enter the URI of a file'),
+    '#description' => t('This must be a stream-type description like public://some_file.txt or http://drupal.org or private://another_file.txt or (for this example) session://yet_another_file.txt.'),
+  );
+  $form['fileops']['read_submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Read the file and store it locally'),
+    '#submit' => array('file_example_read_submit'),
+  );
+  $form['fileops']['delete_submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete file'),
+    '#submit' => array('file_example_delete_submit'),
+  );
+  $form['fileops']['check_submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Check to see if file exists'),
+    '#submit' => array('file_example_file_check_exists_submit'),
+  );
+
+  $form['directory'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Create or prepare a directory'),
+  );
+
+  $form['directory']['directory_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Directory to create/prepare/delete'),
+    '#default_value' => $default_directory,
+    '#description' => t('This is a directory as in public://some/directory or private://another/dir.'),
+  );
+  $form['directory']['create_directory'] = array(
+    '#type' => 'submit',
+    '#value' => t('Create directory'),
+    '#submit' => array('file_example_create_directory_submit'),
+  );
+  $form['directory']['delete_directory'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete directory'),
+    '#submit' => array('file_example_delete_directory_submit'),
+  );
+  $form['directory']['check_directory'] = array(
+    '#type' => 'submit',
+    '#value' => t('Check to see if directory exists'),
+    '#submit' => array('file_example_check_directory_submit'),
+  );
+
+  $form['debug'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Debugging'),
+  );
+  $form['debug']['show_session'] = array(
+    '#type' => 'submit',
+    '#value' => t('Show $_SESSION contents'),
+    '#submit' => array('file_example_show_session_contents_submit'),
+  );
+  $form['debug']['show_raw_session'] = array(
+    '#type' => 'submit',
+    '#value' => t('Show raw $_SESSION contents'),
+    '#submit' => array('file_example_show_session_contents_submit'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler to write a managed file.
+ *
+ * The key functions used here are:
+ * - file_save_data(), which takes a buffer and saves it to a named file and
+ *   also creates a tracking record in the database and returns a file object.
+ *   In this function we use FILE_EXISTS_RENAME (the default) as the argument,
+ *   which means that if there's an existing file, create a new non-colliding
+ *   filename and use it.
+ * - file_create_url(), which converts a URI in the form public://junk.txt or
+ *   private://something/test.txt into a URL like
+ *   http://example.com/sites/default/files/junk.txt.
+ * @ingroup file_example
+ */
+function file_example_managed_write_submit($form, &$form_state) {
+  $data = $form_state['values']['write_contents'];
+  $uri = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
+
+  // Managed operations work with a file object.
+  $file_object = file_save_data($data, $uri, FILE_EXISTS_RENAME);
+  if (!empty($file_object)) {
+    $url = file_create_url($file_object->uri);
+    $_SESSION['file_example_default_file'] = $file_object->uri;
+    drupal_set_message(t('Saved managed file: %file to destination %destination (accessible via !url, actual uri=<span id="uri">@uri</span>)', array('%file' => print_r($file_object, TRUE), '%destination' => $uri, '@uri' => $file_object->uri, '!url' => l(t('this URL'), $url))));
+  }
+  else {
+    drupal_set_message(t('Failed to save the managed file'), 'error');
+  }
+}
+
+/**
+ * Submit handler to write an unmanaged file.
+ *
+ * The key functions used here are:
+ * - file_unmanaged_save_data(), which takes a buffer and saves it to a named
+ *   file, but does not create any kind of tracking record in the database.
+ *   This example uses FILE_EXISTS_REPLACE for the third argument, meaning
+ *   that if there's an existing file at this location, it should be replaced.
+ * - file_create_url(), which converts a URI in the form public://junk.txt or
+ *   private://something/test.txt into a URL like
+ *   http://example.com/sites/default/files/junk.txt.
+ * @ingroup file_example
+ */
+
+function file_example_unmanaged_write_submit($form, &$form_state) {
+  $data = $form_state['values']['write_contents'];
+  $destination = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
+
+  // With the unmanaged file we just get a filename back.
+  $filename = file_unmanaged_save_data($data, $destination, FILE_EXISTS_REPLACE);
+  if ($filename) {
+    $url = file_create_url($filename);
+    $_SESSION['file_example_default_file'] = $filename;
+    drupal_set_message(t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)', array('%filename' => $filename, '@uri' => $filename, '!url' => l(t('this URL'), $url))));
+  }
+  else {
+    drupal_set_message(t('Failed to save the file'), 'error');
+  }
+}
+
+
+
+/**
+ * Submit handler to write an unmanaged file using plain PHP functions.
+ *
+ * The key functions used here are:
+ * - file_unmanaged_save_data(), which takes a buffer and saves it to a named
+ *   file, but does not create any kind of tracking record in the database.
+ * - file_create_url(), which converts a URI in the form public://junk.txt or
+ *   private://something/test.txt into a URL like
+ *   http://example.com/sites/default/files/junk.txt.
+ * - drupal_tempnam() generates a temporary filename for use.
+ * @ingroup file_example
+ */
+
+function file_example_unmanaged_php_submit($form, &$form_state) {
+  $data = $form_state['values']['write_contents'];
+  $destination = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
+
+  if (empty($destination)) {
+    // If no destination has been provided, use a generated name.
+    $destination = drupal_tempnam('public://', 'file');
+  }
+
+  // With all traditional PHP functions we can use the stream wrapper notation
+  // for a file as well.
+  $fp = fopen($destination, 'w');
+
+  // To demonstrate the fact that everything is based on streams, we'll do
+  // multiple 5-character writes to put this to the file. We could easily
+  // (and far more conveniently) write it in a single statement with
+  // fwrite($fp, $data).
+  $length = strlen($data);
+  $write_size = 5;
+  for ($i=0; $i < $length; $i += $write_size) {
+    $result = fwrite($fp, substr($data, $i, $write_size));
+    if ($result === FALSE) {
+      drupal_set_message(t('Failed writing to the file; '), 'error');
+      fclose($fp);
+      return;
+    }
+  }
+  $url = file_create_url($destination);
+  $_SESSION['file_example_default_file'] = $destination;
+  drupal_set_message(t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)', array('%filename' => $destination, '@uri' => $destination, '!url' => l(t('this URL'), $url))));
+}
+
+/**
+ * Submit handler for reading a stream wrapper.
+ *
+ * Drupal now has full support for PHP's stream wrappers, which means that
+ * instead of the traditional use of all the file functions
+ * ($fp = fopen("/tmp/some_file.txt");) far more sophisticated and generalized
+ * (and extensible) things can be opened as if they were files. Drupal itself
+ * provides the public:// and private:// schemes for handling public and
+ * private files. PHP provides file:// (the default) and http://, so that a
+ * URL can be read or written (as in a POST) as if it were a file. In addition,
+ * new schemes can be provided for custom applications, as will be demonstrated
+ * below.
+ *
+ * Here we take the stream wrapper provided in the form. We grab the
+ * contents with file_get_contents(). Notice that's it's as simple as that:
+ * file_get_contents("http://example.com") or
+ * file_get_contents("public://somefile.txt") just works. Although it's
+ * not necessary, we use file_unmanaged_save_data() to save this file locally
+ * and then find a local URL for it by using file_create_url().
+ * @ingroup file_example
+ */
+function file_example_read_submit($form, &$form_state) {
+  $uri = $form_state['values']['fileops_file'];
+
+  if (!is_file($uri)) {
+    drupal_set_message(t('The file %uri does not exist', array('%uri' => $uri)), 'error');
+    return;
+  }
+
+  // Make a working filename to save this by stripping off the (possible)
+  // file portion of the streamwrapper. If it's an evil file extension,
+  // file_munge_filename() will neuter it.
+  $filename = file_munge_filename(preg_replace('@^.*/@', '', $uri), '', TRUE);
+  $buffer = file_get_contents($uri);
+
+  if ($buffer) {
+    $sourcename = file_unmanaged_save_data($buffer, 'public://' . $filename);
+    if ($sourcename) {
+      $url = file_create_url($sourcename);
+      $_SESSION['file_example_default_file'] = $sourcename;
+      drupal_set_message(t('The file was read and copied to %filename which is accessible at !url', array('%filename' => $sourcename, '!url' => l($url, $url))));
+    }
+    else {
+      drupal_set_message(t('Failed to save the file'));
+    }
+  }
+  else { // We failed to get the contents of the requested file
+    drupal_set_message(t('Failed to retrieve the file %file', array('%file' => $uri)));
+  }
+}
+
+/**
+ * Submit handler to delete a file.
+ */
+function file_example_delete_submit($form, &$form_state) {
+
+  $uri = $form_state['values']['fileops_file'];
+
+  // Since we don't know if the file is managed or not, look in the database
+  // to see. Normally, code would be working with either managed or unmanaged
+  // files, so this is not a typical situation.
+  $file_object = file_example_get_managed_file($uri);
+
+  // If a managed file, use file_delete().
+  if (!empty($file_object)) {
+    $result = file_delete($file_object);
+    if ($result !== TRUE) {
+      drupal_set_message(t('Failed deleting managed file %uri. Result was %result', array('%uri' => $uri, '%result' => print_r($result, TRUE))), 'error');
+    }
+    else {
+      drupal_set_message(t('Successfully deleted managed file %uri', array('%uri' => $uri)));
+      $_SESSION['file_example_default_file'] = $uri;
+    }
+  }
+  // else use file_unmanaged_delete().
+  else {
+    $result = file_unmanaged_delete($uri);
+    if ($result !== TRUE) {
+      drupal_set_message(t('Failed deleting unmanaged file %uri', array('%uri' => $uri, 'error')));
+    }
+    else {
+      drupal_set_message(t('Successfully deleted unmanaged file %uri', array('%uri' => $uri)));
+      $_SESSION['file_example_default_file'] = $uri;
+    }
+  }
+}
+
+/**
+ * Submit handler to check existence of a file.
+ */
+function file_example_file_check_exists_submit($form, &$form_state) {
+  $uri = $form_state['values']['fileops_file'];
+  if (is_file($uri)) {
+    drupal_set_message(t('The file %uri exists.', array('%uri' => $uri)));
+  }
+  else {
+    drupal_set_message(t('The file %uri does not exist.', array('%uri' => $uri)));
+  }
+
+}
+/**
+ * Submit handler for directory creation.
+ * Here we create a directory and set proper permissions on it using
+ * file_prepare_directory().
+ * @ingroup file_example
+ */
+function file_example_create_directory_submit($form, &$form_state) {
+  $directory = $form_state['values']['directory_name'];
+
+  // The options passed to file_prepare_directory are a bitmask, so we can
+  // specify either FILE_MODIFY_PERMISSIONS (set permissions on the directory),
+  // FILE_CREATE_DIRECTORY, or both together:
+  // FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY.
+  // FILE_MODIFY_PERMISSIONS will set the permissions of the directory by
+  // by default to 0755, or to the value of the variable 'file_chmod_directory'.
+  if (!file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY)) {
+    drupal_set_message(t('Failed to create %directory.', array('%directory' => $directory)), 'error');
+  }
+  else {
+    $result = is_dir($directory);
+    drupal_set_message(t('Directory %directory is ready for use.', array('%directory' => $directory)));
+    $_SESSION['file_example_default_directory'] = $directory;
+  }
+}
+
+/**
+ * Submit handler for directory deletion.
+ *
+ * @see file_unmanaged_delete_recursive()
+ *
+ * @ingroup file_example
+ */
+function file_example_delete_directory_submit($form, &$form_state) {
+  $directory = $form_state['values']['directory_name'];
+
+  $result = file_unmanaged_delete_recursive($directory);
+  if (!$result) {
+    drupal_set_message(t('Failed to delete %directory.', array('%directory' => $directory)), 'error');
+  }
+  else {
+    drupal_set_message(t('Recursively deleted directory %directory.', array('%directory' => $directory)));
+    $_SESSION['file_example_default_directory'] = $directory;
+  }
+}
+
+/**
+ * Submit handler to test directory existence.
+ * This actually just checks to see if the directory is writable
+ * @param unknown_type $form
+ * @param unknown_type $form_state
+ */
+function file_example_check_directory_submit($form, &$form_state) {
+  $directory = $form_state['values']['directory_name'];
+  $result = is_dir($directory);
+  if (!$result) {
+    drupal_set_message(t('Directory %directory does not exist.', array('%directory' => $directory)));
+  }
+  else {
+    drupal_set_message(t('Directory %directory exists.', array('%directory' => $directory)));
+  }
+}
+
+/**
+ * Utility submit function for debugging: Show $_SESSION.
+ */
+function file_example_show_session_contents_submit($form, &$form_state) {
+  if ($form_state['triggering_element']['#value'] == t('Show raw $_SESSION contents')) {
+    drupal_set_message('<pre>' . print_r($_SESSION['file_example'], TRUE) . '</pre>');
+  }
+  else {
+    return file_example_session_contents(NULL);
+  }
+}
+
+/**
+ * Utility function to check for and return a managed file.
+ * In this demonstration code we don't necessarily know if a file is managed
+ * or not, so often need to check to do the correct behavior. Normal code
+ * would not have to do this, as it would be working with either managed or
+ * unmanaged files.
+ *
+ * @param $uri
+ *   The URI of the file, like public://test.txt.
+ */
+function file_example_get_managed_file($uri) {
+  $fid = db_query('SELECT fid FROM {file_managed} WHERE uri = :uri', array(':uri' => $uri))->fetchField();
+  if (!empty($fid)) {
+    $file_object = file_load($fid);
+    return $file_object;
+  }
+  return FALSE;
+}
+
+/**
+ * Implements hook_stream_wrappers().
+ * hook_stream_wrappers() is Drupal's way of exposing the class that PHP will
+ * use to provide a new stream wrapper class. In this case, we'll expose the
+ * 'session' scheme, so a file reference like "session://example/example.txt"
+ * is readable and writable as a location in the $_SESSION variable.
+ *
+ * @see FileExampleSessionStreamWrapper
+ *
+ * defgroup streamwrapper_example Stream Wrapper Example
+ */
+function file_example_stream_wrappers() {
+  $wrappers = array(
+    'session' => array(
+      'name' => t('Session variable storage'),
+      'class' => 'FileExampleSessionStreamWrapper',
+      'description' => t('Store files in the $_SESSION variable as an example.'),
+    ),
+  );
+  return $wrappers;
+}
+
+/**
+ * A utility function to allow us to see what is in a session "file".
+ *
+ * @param $session_path
+ *   The path, as in 'example/some/thing.txt'. This actually is a $_SESSION
+ *   key. example/some/thing.txt would be
+ *   $_SESSION['file_example']['some']['thing.txt']
+ */
+function file_example_session_contents() {
+  if (module_exists('devel')) {
+    dsm($_SESSION['file_example'], t('Entire $_SESSION["file_example"]'));
+  }
+  $path_components = func_get_args();
+  $session_path = join('/', $path_components);
+  $content = file_get_contents('session://' . $session_path);
+  return t('Contents of ') . check_plain($session_path) . ': ' . print_r($content, TRUE);
+}
+
diff --git file_example/file_example.test file_example/file_example.test
new file mode 100644
index 0000000..d6bb3ca
--- /dev/null
+++ file_example/file_example.test
@@ -0,0 +1,124 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Tests for File Example.
+ */
+
+class FileExampleTest extends DrupalWebTestCase {
+
+  protected $privileged_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => t('File Example Functionality'),
+      'description' => t('Test File Example features and sample streamwrapper.'),
+      'group' => 'Examples',
+    );
+  }
+  function setUp() {
+    parent::setUp(array('file_example'));
+    $privileged_user = $this->drupalCreateUser(array('use file example'));
+    $this->drupalLogin($privileged_user);
+  }
+
+  /**
+   * Test the basic File Example UI.
+   * - Create a directory to work with
+   * - Foreach scheme create and read files using each of the three methods.
+   */
+  function testFileExampleBasic() {
+
+    $expected_text = array(
+      t('Write managed file') => t('Saved managed file'),
+      t('Write unmanaged file') => t('Saved file as'),
+      t('Unmanaged using PHP') => t('Saved file as'),
+    );
+    // For each of the three buttons == three write types
+    foreach (array(t('Write managed file'), t('Write unmanaged file'), t('Unmanaged using PHP')) as $button) {
+      // For each scheme supported by Drupal + the session:// wrapper
+      foreach (array('public', 'private', 'temporary', 'session') as $scheme) {
+        // Create a directory for use.
+        $dirname = $scheme . '://' . $this->randomName(10);
+
+        // Directory does not yet exist; assert that.
+        $edit = array(
+          'directory_name' => $dirname,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), t('Verify that directory does not exist.'));
+
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Create directory'));
+        $this->assertRaw(t('Directory %dirname is ready for use', array('%dirname' => $dirname)));
+
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname exists', array('%dirname' => $dirname)), t('Verify that directory now does exist.'));
+
+        // Create a file in the directory we created.
+        $content = $this->randomName(30);
+        $filename = $dirname . '/' . $this->randomName(30) . '.txt';
+
+        // Assert that the file we're about to create does not yet exist.
+        $edit = array(
+          'fileops_file' => $filename,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), t('Verify that file does not yet exist.'));
+
+        debug(t('Processing button=%button, scheme=%scheme, dir=%dirname, file=%filename', array('%button' => $button, '%scheme' => $scheme, '%filename' => $filename, '%dirname' => $dirname)));
+        $edit = array(
+          'write_contents' => $content,
+          'destination' => $filename,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, $button);
+        $this->assertText($expected_text[$button]);
+
+
+        // Capture the name of the output file, as it might have changed due
+        // to file renaming.
+        $element = $this->xpath('//span[@id="uri"]');
+        $output_filename = (string)$element[0];
+        debug($output_filename, 'Name of output file');
+
+        // Click the link provided that is an easy way to get the data for
+        // checking and make sure that the data we put in is what we get out.
+        if (!in_array($scheme, array('private', 'temporary'))) {
+          $this->clickLink(t('this URL'));
+          $this->assertText($content);
+        }
+
+        // Verify that the file exists.
+        $edit = array(
+          'fileops_file' => $filename,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename exists', array('%filename' => $filename)), t('Verify that file now exists.'));
+
+        // Now read the file that got written above and verify that we can use
+        // the writing tools.
+        $edit = array(
+          'fileops_file' => $output_filename,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Read the file and store it locally'));
+
+        $this->assertText(t('The file was read and copied'));
+
+        $edit = array(
+          'fileops_file' => $filename,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Delete file'));
+        $this->assertText(t('Successfully deleted'));
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), t('Verify file has been deleted.'));
+
+        $edit = array(
+          'directory_name' => $dirname,
+        );
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Delete directory'));
+        $this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), t('Verify that directory does not exist after deletion.'));
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git file_example/file_example_session_streams.inc file_example/file_example_session_streams.inc
new file mode 100644
index 0000000..9283793
--- /dev/null
+++ file_example/file_example_session_streams.inc
@@ -0,0 +1,660 @@
+<?php
+// $Id$
+
+/**
+ * @file file_example_session_streams.inc
+ * Implement DrupalStreamWrapperInterface to provide a demonstration session://
+ * streamwrapper. This example is nearly fully functional, but has no known
+ * practical use. It's an example and demonstration only.
+ */
+
+/**
+ * Example stream wrapper class to handle session:// streams.
+ * This is just an example, as it could have horrible results if much
+ * information were placed in the $_SESSION variable. However, it does
+ * demonstrate both the read and write implementation of a stream wrapper.
+ *
+ * A "stream" is an important Unix concept for the reading and writing of
+ * files and other devices. Reading or writing a "stream" just means that you
+ * open some device, file, internet site, or whatever, and you don't have to
+ * know at all what it is. All the functions that deal with it are the same.
+ * You can read/write more from/to the stream, seek a position in the stream,
+ * or anything else without the code that does it even knowing what kind
+ * of device it is talking to. This Unix idea is extended into PHP's
+ * mindset.
+ *
+ * The idea of "stream wrapper" is that this can be extended indefinitely.
+ * The classic example is HTTP: With PHP you can do a
+ * file_get_contents("http://drupal.org/projects") as if it were a file,
+ * because the scheme "http" is supported natively in PHP. So Drupal adds
+ * the public:// and private:// schemes, and contrib modules can add any
+ * scheme they want to. This example adds the session:// scheme, which allows
+ * reading and writing the $_SESSION['file_example'] key as if it were a file.
+ */
+class FileExampleSessionStreamWrapper implements DrupalStreamWrapperInterface {
+  /**
+   * Stream context resource.
+   *
+   * @var Resource
+   */
+  public $context;
+
+
+  /**
+   * Instance URI (stream).
+   *
+   * These streams will be references as 'session://example_target'
+   *
+   * @var String
+   */
+  protected $uri;
+
+  /**
+   * The content of the stream.
+   * Since this trivial example just uses the $_SESSION variable, this is
+   * simply a reference to the contents of the related part of
+   * $_SESSION['file_example'].
+   */
+  protected $session_content;
+
+  /**
+   * Pointer to where we are in a directory read.
+   */
+  protected $directory_pointer;
+
+  /**
+   * List of keys in a given directory.
+   */
+  protected $directory_keys;
+
+  /**
+   * The pointer to the next read or write within the session variable.
+   */
+  protected $stream_pointer;
+
+  public function __construct() {
+    $_SESSION['file_example']['.isadir.txt'] = TRUE;
+  }
+
+  /**
+   * Implements setUri().
+   */
+  function setUri($uri) {
+    $this->uri = $uri;
+  }
+
+  /**
+   * Implements getUri().
+   */
+  function getUri() {
+    return $this->uri;
+  }
+
+  /**
+   *  Implements getTarget().
+   *  The "target" is the portion of the URI to the right of the scheme.
+   *  So in session://example/test.txt, the target is 'example/test.txt'.
+   */
+  function getTarget($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    list($scheme, $target) = explode('://', $uri, 2);
+
+    // Remove erroneous leading or trailing, forward-slashes and backslashes.
+    // In the session:// scheme, there is never a leading slash on the target.
+    return trim($target, '\/');
+  }
+
+  /**
+   * Implements getMimeType().
+   */
+  static function getMimeType($uri, $mapping = NULL) {
+    if (!isset($mapping)) {
+      // The default file map, defined in file.mimetypes.inc is quite big.
+      // We only load it when necessary.
+      include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
+      $mapping = file_mimetype_mapping();
+    }
+
+    $extension = '';
+    $file_parts = explode('.', basename($uri));
+
+    // Remove the first part: a full filename should not match an extension.
+    array_shift($file_parts);
+
+    // Iterate over the file parts, trying to find a match.
+    // For my.awesome.image.jpeg, we try:
+    //   - jpeg
+    //   - image.jpeg, and
+    //   - awesome.image.jpeg
+    while ($additional_part = array_pop($file_parts)) {
+      $extension = strtolower($additional_part . ($extension ? '.' . $extension : ''));
+      if (isset($mapping['extensions'][$extension])) {
+        return $mapping['mimetypes'][$mapping['extensions'][$extension]];
+      }
+    }
+
+    return 'application/octet-stream';
+  }
+
+  /**
+   * Implements getDirectoryPath().
+   * In this case there is no directory string, so return an empty string.
+   */
+  public function getDirectoryPath() {
+    return '';
+  }
+
+  /**
+   * Overrides getExternalUrl().
+   * We have set up a helper function and menu entry to provide access to this
+   * key via HTTP; normally it would be accessible some other way.
+   */
+  function getExternalUrl() {
+    $path = $this->getLocalPath();
+    $url = url('examples/file_example/access_session/' . $path, array('absolute' => TRUE));
+    return $url;
+  }
+
+  /**
+   * We have no concept of chmod, so just return TRUE.
+   */
+  function chmod($mode) {
+    return TRUE;
+  }
+
+  /**
+   * Implements realpath().
+   */
+  function realpath() {
+    return 'session://' . $this->getLocalPath();
+  }
+
+  /**
+   * Returns the local path.
+   * Here we aren't doing anything but stashing the "file" in a key in the
+   * $_SESSION variable, so there's not much to do but to create a "path"
+   * which is really just a key in the $_SESSION variable. So something
+   * like 'session://one/two/three.txt' becomes
+   * $_SESSION['file_example']['one']['two']['three.txt'] and the actual path
+   * is "one/two/three.txt".
+   *
+   * @param $uri
+   *   Optional URI, supplied when doing a move or rename.
+   */
+  protected function getLocalPath($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    $path  = str_replace('session://', '', $uri);
+    $path = trim($path, '/');
+    return $path;
+  }
+
+  /**
+   * Opens a stream, as for fopen(), file_get_contents(), file_put_contents()
+   *
+   * @param $uri
+   *   A string containing the URI to the file to open.
+   * @param $mode
+   *   The file mode ("r", "wb" etc.).
+   * @param $options
+   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+   * @param &$opened_path
+   *   A string containing the path actually opened.
+   *
+   * @return
+   *   Returns TRUE if file was opened successfully. (Always returns TRUE).
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-open.php
+   */
+  public function stream_open($uri, $mode, $options, &$opened_path) {
+    $this->uri = $uri;
+
+    // We make $session_content a reference to the appropriate key in the
+    // $_SESSION variable. So if the local path were
+    // /example/test.txt it $session_content would now be a
+    // reference to $_SESSION['file_example']['example']['test.txt'].
+    $this->session_content = &$this->uri_to_session_key($uri);
+
+    // Reset the stream pointer since this is an open.
+    $this->stream_pointer = 0;
+    return TRUE;
+  }
+
+  /**
+   * Return a reference to the correct $_SESSION key.
+   * @param $uri
+   *   The uri: session://something
+   * @param $create
+   *   If TRUE, create the key
+   *
+   * @return
+   *   TRUE if the key exists (which it will if $create was set)
+   */
+  protected function &uri_to_session_key($uri, $create = TRUE) {
+    $path = $this->getLocalPath($uri);
+    $path_components = preg_split('/\//', $path);
+    $fail = FALSE;
+    $var = &$_SESSION['file_example'];
+    foreach ($path_components as $component) {
+      if ($create || isset($var[$component])) {
+        $var = &$var[$component];
+      }
+      else {
+        return $fail;
+      }
+    }
+    return $var;
+  }
+  /**
+   * Support for flock().
+   * The $_SESSION variable has no locking capability, so return TRUE.
+   *
+   * @param $operation
+   *   One of the following:
+   *   - LOCK_SH to acquire a shared lock (reader).
+   *   - LOCK_EX to acquire an exclusive lock (writer).
+   *   - LOCK_UN to release a lock (shared or exclusive).
+   *   - LOCK_NB if you don't want flock() to block while locking (not
+   *     supported on Windows).
+   *
+   * @return
+   *   Always returns TRUE at the present time. (no support)
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-lock.php
+   */
+  public function stream_lock($operation) {
+    return TRUE;
+  }
+
+  /**
+   * Support for fread(), file_get_contents() etc.
+   *
+   * @param $count
+   *   Maximum number of bytes to be read.
+   *
+   * @return
+   *   The string that was read, or FALSE in case of an error.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-read.php
+   */
+  public function stream_read($count) {
+    if (is_string($this->session_content)) {
+      $remaining_chars = strlen($this->session_content) - $this->stream_pointer;
+      $number_to_read = min($count, $remaining_chars);
+      if ($remaining_chars > 0) {
+        $buffer = substr($this->session_content, $this->stream_pointer, $number_to_read);
+        $this->stream_pointer += $number_to_read;
+        return $buffer;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * @param $data
+   *   The string to be written.
+   *
+   * @return
+   *   The number of bytes written (integer).
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-write.php
+   */
+  public function stream_write($data) {
+    // Sanitize the data in a simple way since we're putting it into the
+    // session variable.
+    $data = check_plain($data);
+    $this->session_content = substr_replace($this->session_content, $data, $this->stream_pointer);
+    $this->stream_pointer += strlen($data);
+    return strlen($data);
+  }
+
+  /**
+   * Support for feof().
+   *
+   * @return
+   *   TRUE if end-of-file has been reached.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-eof.php
+   */
+  public function stream_eof() {
+    return FALSE;
+  }
+
+  /**
+   * Support for fseek().
+   *
+   * @param $offset
+   *   The byte offset to got to.
+   * @param $whence
+   *   SEEK_SET, SEEK_CUR, or SEEK_END.
+   *
+   * @return
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-seek.php
+   */
+  public function stream_seek($offset, $whence) {
+    if (strlen($this->session_content) >= $offset) {
+      $this->stream_pointer = $offset;
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * @return
+   *   TRUE if data was successfully stored (or there was no data to store).
+   *   This always returns TRUE, as this example provides and needs no
+   *   flush support.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-flush.php
+   */
+  public function stream_flush() {
+    return TRUE;
+  }
+
+  /**
+   * Support for ftell().
+   *
+   * @return
+   *   The current offset in bytes from the beginning of file.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-tell.php
+   */
+  public function stream_tell() {
+    return $this->stream_pointer;
+  }
+
+  /**
+   * Support for fstat().
+   *
+   * @return
+   *   An array with file status, or FALSE in case of an error - see fstat()
+   *   for a description of this array.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-stat.php
+   */
+  public function stream_stat() {
+    return array();
+  }
+
+  /**
+   * Support for fclose().
+   *
+   * @return
+   *   TRUE if stream was successfully closed.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-close.php
+   */
+  public function stream_close() {
+    $this->stream_pointer = 0;
+    unset($this->session_content); // Unassign the reference.
+    return TRUE;
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * @param $uri
+   *   A string containing the uri to the resource to delete.
+   *
+   * @return
+   *   TRUE if resource was successfully deleted.
+   *
+   * @see http://php.net/manual/en/streamwrapper.unlink.php
+   */
+  public function unlink($uri) {
+    $path = $this->getLocalPath($uri);
+    $path_components = preg_split('/\//', $path);
+    $fail = FALSE;
+    $unset = '$_SESSION[\'file_example\']';
+    foreach ($path_components as $component) {
+      $unset .= '[\'' . $component . '\']';
+    }
+    // TODO: I really don't like this eval.
+    debug($unset, 'array element to be unset');
+    eval("unset($unset);");
+
+    return TRUE;
+  }
+
+  /**
+   * Support for rename().
+   *
+   * @param $from_uri,
+   *   The uri to the file to rename.
+   * @param $to_uri
+   *   The new uri for file.
+   *
+   * @return
+   *   TRUE if file was successfully renamed.
+   *
+   * @see http://php.net/manual/en/streamwrapper.rename.php
+   */
+  public function rename($from_uri, $to_uri) {
+    $from_key = &$this->uri_to_session_key($from_uri);
+    $to_key = &$this->uri_to_session_key($to_uri);
+    if (is_dir($to_key) || is_file($to_key)) {
+      return FALSE;
+    }
+    $to_key = $from_key;
+    unset($from_key);
+    return TRUE;
+  }
+
+  /**
+   * Gets the name of the directory from a given path.
+   *
+   * @param $uri
+   *   A URI.
+   *
+   * @return
+   *   A string containing the directory name.
+   *
+   * @see drupal_dirname()
+   */
+  public function dirname($uri = NULL) {
+    list($scheme, $target) = explode('://', $uri, 2);
+    $target  = $this->getTarget($uri);
+    if (strpos($target, '/')) {
+      $dirname = preg_replace('@/[^/]*$@', '', $target);
+    }
+    else
+    {
+      $dirname = '';
+    }
+    return $scheme . '://' . $dirname;
+  }
+
+  /**
+   * Support for mkdir().
+   *
+   * @param $uri
+   *   A string containing the URI to the directory to create.
+   * @param $mode
+   *   Permission flags - see mkdir().
+   * @param $options
+   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   *
+   * @return
+   *   TRUE if directory was successfully created.
+   *
+   * @see http://php.net/manual/en/streamwrapper.mkdir.php
+   */
+  public function mkdir($uri, $mode, $options) {
+    // If this already exists, then we can't mkdir.
+    if (is_dir($uri) || is_file($uri)) {
+      return FALSE;
+    }
+
+    // Create the key in $_SESSION;
+    $this->uri_to_session_key($uri, TRUE);
+
+    // Place a magic file inside it to differentiate this from an empty file.
+    $marker_uri = $uri . '/.isadir.txt';
+    $this->uri_to_session_key($marker_uri, TRUE);
+    return TRUE;
+  }
+
+  /**
+   * Support for rmdir().
+   *
+   * @param $uri
+   *   A string containing the URI to the directory to delete.
+   * @param $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   *
+   * @return
+   *   TRUE if directory was successfully removed.
+   *
+   * @see http://php.net/manual/en/streamwrapper.rmdir.php
+   */
+  public function rmdir($uri, $options) {
+    $path = $this->getLocalPath($uri);
+    $path_components = preg_split('/\//', $path);
+    $fail = FALSE;
+    $unset = '$_SESSION[\'file_example\']';
+    foreach ($path_components as $component) {
+      $unset .= '[\'' . $component . '\']';
+    }
+    // TODO: I really don't like this eval.
+    debug($unset, 'array element to be unset');
+    eval("unset($unset);");
+
+    return TRUE;
+  }
+
+  /**
+   * Support for stat().
+   * This important function goes back to the Unix way of doing things.
+   * In this example almost the entire stat array is irrelevant, but the
+   * mode is very important. It tells PHP whether we have a file or a
+   * directory and what the permissions are. All that is packed up in a
+   * bitmask. This is not normal PHP fodder.
+   *
+   * @param $uri
+   *   A string containing the URI to get information about.
+   * @param $flags
+   *   A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
+   *
+   * @return
+   *   An array with file status, or FALSE in case of an error - see fstat()
+   *   for a description of this array.
+   *
+   * @see http://php.net/manual/en/streamwrapper.url-stat.php
+   */
+  public function url_stat($uri, $flags) {
+
+    $key = $this->uri_to_session_key($uri, FALSE);
+    $return = FALSE; // Default to fail.
+    $mode = 0;
+
+    // We will call an array a directory and the root is always an array.
+    if (is_array($key) && array_key_exists('.isadir.txt', $key)) {
+      $mode = 0040000; // S_IFDIR means it's a directory.
+    }
+    else if ($key !== FALSE) {
+      $mode = 0100000; // S_IFREG, means it's a file.
+    }
+
+    if ($mode) {
+      $mode |= 0777;  // There are no protections on this, so all writable.
+      $return = array(
+        'dev' => 0,
+        'ino' => 0,
+        'mode' => $mode,
+        'nlink' => 0,
+        'uid' => 0,
+        'gid' => 0,
+        'rdev' => 0,
+        'size' => 0,
+        'atime' => 0,
+        'mtime' => 0,
+        'ctime' => 0,
+        'blksize' => 0,
+        'blocks' => 0,
+      );
+    }
+    return $return;
+  }
+
+  /**
+   * Support for opendir().
+   *
+   * @param $uri
+   *   A string containing the URI to the directory to open.
+   * @param $options
+   *   Unknown (parameter is not documented in PHP Manual).
+   *
+   * @return
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-opendir.php
+   */
+  public function dir_opendir($uri, $options) {
+    $var = &$this->uri_to_session_key($uri, FALSE);
+    if ($var === FALSE || !array_key_exists('.isadir.txt', $var)) {
+      return FALSE;
+    }
+
+    // We grab the list of key names, flip it so that .isadir.txt can easily
+    // be removed, then flip it back so we can easily walk it as a list.
+    $this->directory_keys = array_flip(array_keys($var));
+    unset($this->directory_keys['.isadir.txt']);
+    $this->directory_keys = array_keys($this->directory_keys);
+    $this->directory_pointer = 0;
+    return TRUE;
+  }
+
+  /**
+   * Support for readdir().
+   *
+   * @return
+   *   The next filename, or FALSE if there are no more files in the directory.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-readdir.php
+   */
+  public function dir_readdir() {
+    if ($this->directory_pointer < count($this->directory_keys)) {
+      $next = $this->directory_keys[$this->directory_pointer];
+      $this->directory_pointer++;
+      return $next;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for rewinddir().
+   *
+   * @return
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
+   */
+  public function dir_rewinddir() {
+    $this->directory_pointer = 0;
+  }
+
+  /**
+   * Support for closedir().
+   *
+   * @return
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
+   */
+  public function dir_closedir() {
+    $this->directory_pointer = 0;
+    unset($this->directory_keys);
+    return TRUE;
+  }
+}
