diff --git a/file_example/file_example.info.yml b/file_example/file_example.info.yml
new file mode 100644
index 0000000..e66a63e
--- /dev/null
+++ b/file_example/file_example.info.yml
@@ -0,0 +1,7 @@
+name: File example
+type: module
+description: Examples of using the Drupal File API and Stream Wrappers.
+package: Example modules
+core: 8.x
+dependencies:
+  - examples
\ No newline at end of file
diff --git a/file_example/file_example.module b/file_example/file_example.module
new file mode 100644
index 0000000..96197c0
--- /dev/null
+++ b/file_example/file_example.module
@@ -0,0 +1,25 @@
+<?php
+/**
+ * @file
+ * Examples demonstrating the Drupal File API (and Stream Wrappers).
+ */
+
+/**
+ * @defgroup file_example Example: Files
+ * @ingroup examples
+ * @{
+ * 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.
+ */
+
+/**
+ * @} End of "defgroup file_example".
+ */
diff --git a/file_example/file_example.permissions.yml b/file_example/file_example.permissions.yml
new file mode 100644
index 0000000..340e6e3
--- /dev/null
+++ b/file_example/file_example.permissions.yml
@@ -0,0 +1,2 @@
+'use file example':
+  title: Use the examples in the File Example module.
diff --git a/file_example/file_example.routing.yml b/file_example/file_example.routing.yml
new file mode 100644
index 0000000..5f54179
--- /dev/null
+++ b/file_example/file_example.routing.yml
@@ -0,0 +1,23 @@
+file_example.description:
+  path: '/examples/file_example'
+  defaults:
+    _controller: '\Drupal\file_example\Controller\FileExampleController::description'
+    _title: 'File Example'
+  requirements:
+    _permission: 'access content'
+
+filed_example.fileapi:
+  path: '/examples/file_example/fileapi'
+  defaults:
+    _form: '\Drupal\file_example\Form\FileExampleReadWriteForm'
+    _title: 'Use File API to read/write a file'
+  requirements:
+    _permission: 'use file example'
+
+file_example.access_session:
+  path: '/examples/file_example/access_session'
+  defaults:
+    _controller: '\Drupal\file_example\Controller\FileExampleController::accessSession'
+    _title: 'Session Accessor'
+  requirements:
+    _permission: 'use file example'
diff --git a/file_example/file_example.services.yml b/file_example/file_example.services.yml
new file mode 100644
index 0000000..ae9a0f3
--- /dev/null
+++ b/file_example/file_example.services.yml
@@ -0,0 +1,11 @@
+#
+# To implement a stream wrapper, we need to register it with the system.  We can either do this
+# manually by calling up the 'stream_wrapper.manager' service, or, as we do here, have
+# the system autoload it by tagging the service.
+#
+services:
+  file_example.stream_wrapper:
+    class: Drupal\file_example\StreamWrapper\FileExampleSessionStreamWrapper
+    tags:
+      - { name: stream_wrapper, scheme: session }
+  
\ No newline at end of file
diff --git a/file_example/src/Controller/FileExampleController.php b/file_example/src/Controller/FileExampleController.php
new file mode 100644
index 0000000..f518dde
--- /dev/null
+++ b/file_example/src/Controller/FileExampleController.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\file_example\Controller\FileExampleController.
+ */
+
+namespace Drupal\file_example\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for file example routes.
+ */
+class FileExampleController extends ControllerBase {
+
+  /**
+   * A simple controller method to explain what the file example is about.
+   */
+  public function description() {
+
+    // Put the link into the content.
+    $build = array(
+      '#markup' => $this->t('The file example module provides a form and code to demonstrate the Drupal 7 file api. Experiment with the form, and then look at the submit handlers in the code to understand the file api.'),
+    );
+
+    return $build;
+  }
+  
+  /**
+   * Session handler
+   *
+   * Parameters are variable, since we are using this to support a simulated file system built on sessions.
+   *
+   * @todo Figure out how routing works.  Symfony wants routes defined in advance, and won't just give
+   *   you the path the way D7 did. So this is a research topic.  I think we'll need to keep it
+   *   simple and use a query string (?path=), since the Drupal router really does NOT want to do this;
+   *   see https://www.drupal.org/node/1827544.
+   */
+  public function accessSession() {
+    //almost certainly wrong:
+    return Response(NULL, 404);
+  }
+
+}
diff --git a/file_example/src/Form/FileExampleReadWriteForm.php b/file_example/src/Form/FileExampleReadWriteForm.php
new file mode 100644
index 0000000..6704210
--- /dev/null
+++ b/file_example/src/Form/FileExampleReadWriteForm.php
@@ -0,0 +1,659 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\file_example\Form\EmailExampleGetFormPage.
+ */
+
+namespace Drupal\file_example\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\file\FileInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Url;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * File test form class.
+ *
+ * @ingroup file_example
+ */
+class FileExampleReadWriteForm extends FormBase {
+
+  /**
+   * Constructs a new FileExampleReadWriteForm page.
+   *
+   */
+  public function __construct() {
+    //todo: we may need to inject a session related object here.
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * This is an override of LinkGeneratorTrait::l to work around
+   * some problems related to handling non-routing URLs.
+   *
+   * @see https://www.drupal.org/node/2539622
+   */
+  protected function l($text, Url $url) {
+    try {
+      $l = parent::l($text, $url);
+      return $l;
+    }
+    catch (\Exception $e) {
+      
+    }
+    return '';
+  }
+ 
+  /**
+   * {@inheritdoc}
+   *
+   * @todo set up dependency injections for sessions.
+   */
+  public static function create(ContainerInterface $container) {
+    //return new static($container->get('plugin.manager.mail'));
+    return new static();
+  }
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormID() {
+    return 'file_example_readwrite';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Remove direct manipulation of the session.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    if (empty($_SESSION['file_example_default_file'])) {
+      $_SESSION['file_example_default_file'] = 'session://drupal.txt';
+    }
+    $default_file = $_SESSION['file_example_default_file'];
+    if (empty($_SESSION['file_example_default_directory'])) {
+      $_SESSION['file_example_default_directory'] = 'session://directory1';
+    }
+    $default_directory = $_SESSION['file_example_default_directory'];
+
+    $form['write_file'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Write to a file'),
+    );
+    $form['write_file']['write_contents'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Enter something you would like to write to a file') . ' ' . date('m'),
+      '#default_value' => $this->t('Put some text here or just use this text'),
+    );
+
+    $form['write_file']['destination'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $default_file,
+      '#title' => $this->t('Optional: Enter the streamwrapper saying where it should be written'),
+      '#description' => $this->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' => $this->t('Write managed file'),
+      '#submit' => array('::handleManagedFile'),
+    );
+    $form['write_file']['unmanaged_submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Write unmanaged file'),
+      '#submit' => array('::handleUnmanagedFile'),
+    );
+    $form['write_file']['unmanaged_php'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Unmanaged using PHP'),
+      '#submit' => array('::handleUnmanagedPHP'),
+    );
+
+    $form['fileops'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Read from a file'),
+    );
+    $form['fileops']['fileops_file'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $default_file,
+      '#title' => $this->t('Enter the URI of a file'),
+      '#description' => $this->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' => $this->t('Read the file and store it locally'),
+      '#submit' => array('::handleFileRead'),
+    );
+    $form['fileops']['delete_submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Delete file'),
+      '#submit' => array('::handleFileDelete'),
+    );
+    $form['fileops']['check_submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Check to see if file exists'),
+      '#submit' => array('::handleFileExists'),
+    );
+
+    $form['directory'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Create or prepare a directory'),
+    );
+
+    $form['directory']['directory_name'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Directory to create/prepare/delete'),
+      '#default_value' => $default_directory,
+      '#description' => $this->t('This is a directory as in public://some/directory or private://another/dir.'),
+    );
+    $form['directory']['create_directory'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Create directory'),
+      '#submit' => array('::handleDirectoryCreate'),
+    );
+    $form['directory']['delete_directory'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Delete directory'),
+      '#submit' => array('::handleDirectoryDelete'),
+    );
+    $form['directory']['check_directory'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Check to see if directory exists'),
+      '#submit' => array('::handleDirectoryExists'),
+    );
+
+    $form['debug'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Debugging'),
+    );
+    $form['debug']['show_raw_session'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Show raw $_SESSION contents'),
+      '#submit' => array('::handleShowSession'),
+    );
+    $form['debug']['reset_session'] = array(
+      '#type' => 'submit',
+      '#value' => t('Reset the Session'),
+      '#submit' => array('::handleResetSession'),
+    );
+    
+    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.
+ */
+  public function handleManagedFile(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $data = $form_values['write_contents'];
+    $uri = !empty($form_values['destination']) ? $form_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 = self::getExternalUrl($file_object);
+      $_SESSION['file_example_default_file'] = $file_object->getFileUri();
+      $file_data = $file_object->toArray();
+      if ($url) {
+        drupal_set_message(
+         $this->t('Saved managed file: %file to destination %destination (accessible via !url, actual uri=<span id="uri">@uri</span>)',
+            array(
+              '%file' => print_r($file_data, TRUE),
+              '%destination' => $uri, '@uri' => $file_object->getFileUri(),
+              '!url' => $this->l(t('this URL'), $url),
+            )
+          )
+        );
+      }
+      else {
+        //this Uri is not routable, so we cannot give a link to it.
+        drupal_set_message(
+         $this->t('Saved managed file: %file to destination %destination (no URL, since this stream type does not support it)',
+            array(
+              '%file' => print_r($file_data, TRUE),
+              '%destination' => $uri, '@uri' => $file_object->getFileUri(),
+            )
+          )
+        );
+
+      }
+    }
+    else {
+      drupal_set_message(t('Failed to save the managed file'), 'error');
+    }
+
+  }
+
+  /**
+   * Helper function to get us an external URL if this is legal, and to catch
+   * the exception Drupal throws if this is not possible.
+   */
+  private static function getExternalUrl($file_object) {
+    if ($file_object instanceof FileInterface) {
+      $uri = $file_object->getFileUri();
+    }
+    else {
+      // a little tricky, since file.inc is a little inconsistent, but often this
+      // is a Uri. See http://drupal.stackexchange.com/questions/177869/how-to-create-a-url-to-an-unmanaged-public-file-in-drupal-8
+      $uri = file_create_url($file_object);
+    }
+   
+    try {
+      $url = Url::fromUri($uri);
+      if ($url->isExternal()) {
+        return $url;
+      }
+      // if the Uri is unroutable (such as for a temporary file), or if Drupal cannot create
+      // a link, we will throw here:
+      $url->toString();
+    }
+    catch (\Exception $e) {
+      return FALSE;
+    }
+    return $url;
+  }
+  
+/**
+ * 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.
+ */
+  public function handleUnmanagedFile(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $data = $form_values['write_contents'];
+    $destination = !empty($form_values['destination']) ? $form_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 = self::getExternalUrl($filename);
+      $_SESSION['file_example_default_file'] = $filename;
+      if ($url) {
+        drupal_set_message(
+         $this->t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)',
+            array(
+              '%filename' => $filename,
+              '@uri' => $filename,
+              '!url' => $this->l(t('this URL'), $url),
+            )
+          )
+        );
+      }
+      else {
+        drupal_set_message(
+         $this->t('Saved file as %filename (not accessible externally)',
+            array(
+              '%filename' => $filename,
+              '@uri' => $filename,
+            )
+          )
+        );        
+      }
+    }
+    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.
+ */
+  public function handleUnmanagedPHP(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $data = $form_values['write_contents'];
+    $destination = !empty($form_values['destination']) ? $form_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 %file', array('%file' => $destination)), 'error');
+        fclose($fp);
+        return;
+      }
+    }
+    $url = self::getExternalUrl($destination);
+    $_SESSION['file_example_default_file'] = $destination;
+    if ($url) {
+      drupal_set_message(
+       $this->t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)',
+          array(
+            '%filename' => $destination,
+            '@uri' => $destination,
+            '!url' => $this->l(t('this URL'), $url),
+          )
+        )
+      );
+    }
+    else {
+      drupal_set_message(
+       $this->t('Saved file as %filename (not accessible externally)',
+          array(
+            '%filename' => $destination,
+            '@uri' => $destination,
+          )
+        )
+      );      
+    }
+
+  }
+
+
+/**
+ * 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().
+ */
+  public function handleFileRead(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $uri = $form_values['fileops_file'];
+
+    if (empty($uri) or !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 = self::getExternalUrl($sourcename);
+        $_SESSION['file_example_default_file'] = $sourcename;
+        if ($url) {
+          //We need to convert the URL to string. Since the URL class throws on non-routables.
+          $url_string = file_create_url($url->getUri());
+          drupal_set_message(
+           $this->t('The file was read and copied to %filename which is accessible at !url',
+              array(
+                '%filename' => $sourcename,
+                '!url' => $this->l($url_string, $url),
+              )
+            )
+          );
+        }
+        else {
+          drupal_set_message(
+           $this->t('The file was read and copied to %filename (not accessible externally)',
+              array(
+                '%filename' => $sourcename,
+              )
+            )
+          );
+          
+        }
+      }
+      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.
+   */
+  public function handleFileDelete(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $uri = $form_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 = self::getManagedFile($uri);
+
+    // If a managed file, use file_delete().
+    if (!empty($file_object)) {
+      // While file_delete should return FALSE on failure,
+      // it can currently throw an exception on certain cache states.
+      $result = FALSE;
+      try {
+        $result = file_delete($file_object);
+      }
+      catch (\Exception $e) {
+        //we should never get here, but as of 8.0rc1, YES WE CAN!
+        error_log('should not get here');
+      }
+      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.
+   */
+  public function handleFileExists(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $uri = $form_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().
+   */
+  public function handleDirectoryCreate(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $directory = $form_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()
+   */
+  public function handleDirectoryDelete(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $directory = $form_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 array $form
+   *   FormAPI form.
+   * @param array $form_state
+   *   FormAPI form state.
+   */
+  public function handleDirectoryExists(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    $directory = $form_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 to show the contents of $_SESSION.
+   */
+  public function handleShowSession(array &$form, FormStateInterface $form_state) {
+    $form_values = $form_state->getValues();
+    // If the devel module is installed, use it's nicer message format.
+    if (\Drupal::moduleHandler()->moduleExists('devel')) {
+      dsm($_SESSION['file_example'],$this->t('Entire $_SESSION["file_example"]'));
+    }
+    else {
+      drupal_set_message('<pre>' . print_r($_SESSION['file_example'], TRUE) . '</pre>');
+    }
+  }
+
+  /**
+   * Utility submit function to show the contents of $_SESSION.
+   */
+  public function handleResetSession(array &$form, FormStateInterface $form_state) {
+    unset($_SESSION['file_example']);
+    unset($_SESSION['file_example_default_file']);
+    unset($_SESSION['file_example_default_directory']);
+    drupal_set_message('Session reset.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    //we don't use this, but the interface requires us to implement it.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    //we don't use this, but the interface requires us to implement it.
+  }
+
+  /**
+  * 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 string $uri
+  *   The URI of the file, like public://test.txt.
+
+  * @return FileInterface|bool
+  *
+  * @todo This should still work. An entity query could be used instead. May be other alternatives.
+  */
+  private static function getManagedFile($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;
+  }
+
+}
diff --git a/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php b/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php
new file mode 100644
index 0000000..64f7b95
--- /dev/null
+++ b/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php
@@ -0,0 +1,864 @@
+<?php
+/**
+ * @file
+ * Provides a demonstration session:// streamwrapper.
+ *
+ * This example is nearly fully functional, but has no known
+ * practical use. It's an example and demonstration only.
+ */
+
+namespace Drupal\file_example\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StreamWrapper;
+use Drupal\Core\Url;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\Html;
+
+/**
+ * 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.
+ *
+ * Note that because this implementation uses simple PHP arrays ($_SESSION)
+ * it is limited to string values, so binary files will not work correctly.
+ * Only text files can be used.
+ *
+ * @ingroup file_example
+ */
+class FileExampleSessionStreamWrapper implements StreamWrapperInterface {
+
+  /**
+   * A generic resource handle.
+   *
+   * @var resource
+   */
+  public $handle = NULL;
+
+
+  /**
+   * 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 $sessionContent;
+
+  /**
+   * Pointer to where we are in a directory read.
+   */
+  protected $directoryPointer;
+
+  /**
+   * List of keys in a given directory.
+   */
+  protected $directoryKeys;
+
+  /**
+   * The pointer to the next read or write within the session variable.
+   */
+  protected $streamPointer;
+
+  /**
+   * Returns the type of stream wrapper.
+   *
+   * @return int
+   */
+  public static function getType() {
+    return StreamWrapperInterface::NORMAL;
+  }
+
+
+  /**
+   * Constructor method.
+   */
+  public function __construct() {
+    $_SESSION['file_example']['.isadir.txt'] = TRUE;
+  }
+
+  /**
+   * Returns the name of the stream wrapper for use in the UI.
+   *
+   * @return string
+   *   The stream wrapper name.
+   */
+  public function getName() {
+    return t('File Example Session files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Simulated file system using your session storage. Not for real use!');
+  }
+
+
+  /**
+   * Implements setUri().
+   */
+  public function setUri($uri) {
+    $this->uri = $uri;
+  }
+
+  /**
+   * Implements getUri().
+   */
+  public 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'.
+   *
+   * @todo Figure out what this is in the new API.
+   */
+  public 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().
+   *
+   * @todo See if we can remove this; it's not part of the new API.
+   */
+  public 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 = Unicode::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.
+   */
+  public function getExternalUrl() {
+    $options = [
+      'absolute' => TRUE,
+      'query' => [
+        'path' => $this->getLocalPath(),
+      ],
+    ];
+    $url = Url::fromRoute('file_example.access_session', [], $options);
+    return $url;
+  }
+
+  /**
+   * We have no concept of chmod, so just return TRUE.
+   */
+  public function chmod($mode) {
+    return TRUE;
+  }
+
+  /**
+   * Returns canonical, absolute path of the resource.
+   *
+   * Implementation placeholder. PHP's realpath() does not support stream
+   * wrappers. We provide this as a default so that individual wrappers may
+   * implement their own solutions.
+   *
+   * @return string
+   *   Returns a string with absolute pathname on success (implemented
+   *   by core wrappers), or FALSE on failure or if the registered
+   *   wrapper does not provide an implementation.
+   */
+  public 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 string $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 string $uri
+   *   A string containing the URI to the file to open.
+   * @param string $mode
+   *   The file mode ("r", "wb" etc.).
+   * @param int $options
+   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+   * @param string &$opened_path
+   *   A string containing the path actually opened.
+   *
+   * @return bool
+   *   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->sessionContent = &$this->uri_to_session_key($uri);
+
+    // Reset the stream pointer since this is an open.
+    $this->streamPointer = 0;
+    return TRUE;
+  }
+
+  /**
+   * Return a reference to the correct $_SESSION key.
+   *
+   * @param string $uri
+   *   The uri: session://something
+   * @param bool $create
+   *   If TRUE, create the key
+   *
+   * @return array|bool
+   *   A reference to the array at the end of the key-path, or
+   *   FALSE if the path doesn't map to a key-path (and $create is FALSE).
+   */
+  protected function &uri_to_session_key($uri, $create = TRUE) {
+    // Since our uri_to_session_key() method returns a reference, we
+    // have to set up a failure flag variable.
+    $fail = FALSE;
+    $path = $this->getLocalPath($uri);
+    $path_components = explode('/', $path);
+    // Set up a reference to the root session:// 'directory.'
+    $var = &$_SESSION['file_example'];
+    // Handle case of just session://.
+    if (count($path_components) == 1 && $path_components[0] === '') {
+      return $var;
+    }
+    // Walk through the path components and create keys in $_SESSION,
+    // unless we're told not to create them.
+    foreach ($path_components as $component) {
+      if ($create || isset($var[$component])) {
+        $var = &$var[$component];
+      }
+      else {
+        // This path doesn't exist as keys, either because the
+        // key doesn't exist, or because we're told not to create it.
+        return $fail;
+      }
+    }
+    return $var;
+  }
+
+  /**
+   * Retrieve the underlying stream resource.
+   *
+   * This method is called in response to stream_select().
+   *
+   * @param int $cast_as
+   *   Can be STREAM_CAST_FOR_SELECT when stream_select() is calling
+   *   stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() is called for
+   *   other uses.
+   *
+   * @return resource|false
+   *   The underlying stream resource or FALSE if stream_select() is not
+   *   supported.
+   *
+   * @see stream_select()
+   * @see http://php.net/manual/streamwrapper.stream-cast.php
+   */
+  public function stream_cast($cast_as) {
+    return FALSE;
+  }
+
+  /**
+   * Sets metadata on the stream.
+   *
+   * @param string $path
+   *   A string containing the URI to the file to set metadata on.
+   * @param int $option
+   *   One of:
+   *   - STREAM_META_TOUCH: The method was called in response to touch().
+   *   - STREAM_META_OWNER_NAME: The method was called in response to chown()
+   *     with string parameter.
+   *   - STREAM_META_OWNER: The method was called in response to chown().
+   *   - STREAM_META_GROUP_NAME: The method was called in response to chgrp().
+   *   - STREAM_META_GROUP: The method was called in response to chgrp().
+   *   - STREAM_META_ACCESS: The method was called in response to chmod().
+   * @param mixed $value
+   *   If option is:
+   *   - STREAM_META_TOUCH: Array consisting of two arguments of the touch()
+   *     function.
+   *   - STREAM_META_OWNER_NAME or STREAM_META_GROUP_NAME: The name of the owner
+   *     user/group as string.
+   *   - STREAM_META_OWNER or STREAM_META_GROUP: The value of the owner
+   *     user/group as integer.
+   *   - STREAM_META_ACCESS: The argument of the chmod() as integer.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure. If $option is not
+   *   implemented, FALSE should be returned.
+   *
+   * @see http://www.php.net/manual/streamwrapper.stream-metadata.php
+   */
+  public function stream_metadata($path, $option, $value) {
+    return FALSE;
+  }
+
+
+   /**
+   * Change stream options.
+   *
+   * This method is called to set options on the stream.
+   *
+   * @param int $option
+   *   One of:
+   *   - STREAM_OPTION_BLOCKING: The method was called in response to
+   *     stream_set_blocking().
+   *   - STREAM_OPTION_READ_TIMEOUT: The method was called in response to
+   *     stream_set_timeout().
+   *   - STREAM_OPTION_WRITE_BUFFER: The method was called in response to
+   *     stream_set_write_buffer().
+   * @param int $arg1
+   *   If option is:
+   *   - STREAM_OPTION_BLOCKING: The requested blocking mode:
+   *     - 1 means blocking.
+   *     - 0 means not blocking.
+   *   - STREAM_OPTION_READ_TIMEOUT: The timeout in seconds.
+   *   - STREAM_OPTION_WRITE_BUFFER: The buffer mode, STREAM_BUFFER_NONE or
+   *     STREAM_BUFFER_FULL.
+   * @param int $arg2
+   *   If option is:
+   *   - STREAM_OPTION_BLOCKING: This option is not set.
+   *   - STREAM_OPTION_READ_TIMEOUT: The timeout in microseconds.
+   *   - STREAM_OPTION_WRITE_BUFFER: The requested buffer size.
+   *
+   * @return bool
+   *   TRUE on success, FALSE otherwise. If $option is not implemented, FALSE
+   *   should be returned.
+   */
+  public function stream_set_option($option, $arg1, $arg2) {
+    return FALSE;
+  }
+
+  /**
+   * Truncate stream.
+   *
+   * Will respond to truncation; e.g., through ftruncate().
+   *
+   * @param int $new_size
+   *   The new size.
+   *
+   * @return bool
+   *   TRUE on success, FALSE otherwise.
+   *
+   * @todo
+   *   This one actually makes sense for the example.
+   */
+  public function stream_truncate($new_size) {
+    return FALSE;
+  }
+
+  /**
+   * Support for flock().
+   *
+   * The $_SESSION variable has no locking capability, so return TRUE.
+   *
+   * @param int $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 bool
+   *   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 int $count
+   *   Maximum number of bytes to be read.
+   *
+   * @return string
+   *   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->sessionContent)) {
+      $remaining_chars = strlen($this->sessionContent) - $this->streamPointer;
+      $number_to_read = min($count, $remaining_chars);
+      if ($remaining_chars > 0) {
+        $buffer = substr($this->sessionContent, $this->streamPointer, $number_to_read);
+        $this->streamPointer += $number_to_read;
+        return $buffer;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * @param string $data
+   *   The string to be written.
+   *
+   * @return int
+   *   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 = Html::escape($data);
+    $this->sessionContent = substr_replace($this->sessionContent, $data, $this->streamPointer);
+    $this->streamPointer += strlen($data);
+    return strlen($data);
+  }
+
+  /**
+   * Support for feof().
+   *
+   * @return bool
+   *   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 int $offset
+   *   The byte offset to got to.
+   * @param int $whence
+   *   SEEK_SET, SEEK_CUR, or SEEK_END.
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-seek.php
+   */
+  public function stream_seek($offset, $whence = SEEK_SET) {
+    if (strlen($this->sessionContent) >= $offset) {
+      $this->streamPointer = $offset;
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * @return bool
+   *   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 int
+   *   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->streamPointer;
+  }
+
+  /**
+   * Support for fstat().
+   *
+   * @return array
+   *   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(
+      'size' => strlen($this->sessionContent),
+    );
+  }
+
+  /**
+   * Support for fclose().
+   *
+   * @return bool
+   *   TRUE if stream was successfully closed.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-close.php
+   */
+  public function stream_close() {
+    $this->streamPointer = 0;
+    // Unassign the reference.
+    unset($this->sessionContent);
+    return TRUE;
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * @param string $uri
+   *   A string containing the uri to the resource to delete.
+   *
+   * @return bool
+   *   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: Is there a better way to delete from an array?
+    // drupal_array_get_nested_value() doesn't work because it only returns
+    // a reference; unsetting a reference only unsets the reference.
+    eval("unset($unset);");
+    return TRUE;
+  }
+
+  /**
+   * Support for rename().
+   *
+   * @param string $from_uri
+   *   The uri to the file to rename.
+   * @param string $to_uri
+   *   The new uri for file.
+   *
+   * @return bool
+   *   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 string $uri
+   *   A URI.
+   *
+   * @return string
+   *   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 string $uri
+   *   A string containing the URI to the directory to create.
+   * @param int $mode
+   *   Permission flags - see mkdir().
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   *
+   * @return bool
+   *   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 string $uri
+   *   A string containing the URI to the directory to delete.
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   *
+   * @return bool
+   *   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 string $uri
+   *   A string containing the URI to get information about.
+   * @param int $flags
+   *   A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
+   *
+   * @return array|bool
+   *   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) {
+    // Get a reference to the $_SESSION key for this URI.
+    $key = $this->uri_to_session_key($uri, FALSE);
+    // Default to fail.
+    $return = FALSE;
+    $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)) {
+      // S_IFDIR means it's a directory.
+      $mode = 0040000;
+    }
+    elseif ($key !== FALSE) {
+      // S_IFREG, means it's a file.
+      $mode = 0100000;
+    }
+
+    if ($mode) {
+      $size = 0;
+      if ($mode == 0100000) {
+        $size = strlen($key);
+      }
+
+      // There are no protections on this, so all writable.
+      $mode |= 0777;
+      $return = array(
+        'dev' => 0,
+        'ino' => 0,
+        'mode' => $mode,
+        'nlink' => 0,
+        'uid' => 0,
+        'gid' => 0,
+        'rdev' => 0,
+        'size' => $size,
+        'atime' => 0,
+        'mtime' => 0,
+        'ctime' => 0,
+        'blksize' => 0,
+        'blocks' => 0,
+      );
+    }
+    return $return;
+  }
+
+  /**
+   * Support for opendir().
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to open.
+   * @param int $options
+   *   Whether or not to enforce safe_mode (0x04).
+   *
+   * @return bool
+   *   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->directoryKeys = array_flip(array_keys($var));
+    unset($this->directoryKeys['.isadir.txt']);
+    $this->directoryKeys = array_keys($this->directoryKeys);
+    $this->directoryPointer = 0;
+    return TRUE;
+  }
+
+  /**
+   * Support for readdir().
+   *
+   * @return string|bool
+   *   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->directoryPointer < count($this->directoryKeys)) {
+      $next = $this->directoryKeys[$this->directoryPointer];
+      $this->directoryPointer++;
+      return $next;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Support for rewinddir().
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
+   */
+  public function dir_rewinddir() {
+    $this->directoryPointer = 0;
+  }
+
+  /**
+   * Support for closedir().
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/en/streamwrapper.dir-closedir.php
+   */
+  public function dir_closedir() {
+    $this->directoryPointer = 0;
+    unset($this->directoryKeys);
+    return TRUE;
+  }
+}
diff --git a/file_example/src/Tests/FileExampleTest.php b/file_example/src/Tests/FileExampleTest.php
new file mode 100644
index 0000000..f2cb67b
--- /dev/null
+++ b/file_example/src/Tests/FileExampleTest.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * @file
+ *  Contains Drupal\file_example\Tests\FileExampleTest.
+ * 
+ * Tests for File Example.
+ */
+
+namespace Drupal\file_example\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Functional tests for the File Example module.
+ *
+ * @ingroup file_example
+ * @group file_example
+ * @group examples
+ */
+class FileExampleTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('file_example', 'file');
+
+  protected $priviledgedUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp(array('file_example'));
+    $this->priviledgedUser = $this->drupalCreateUser(array('use file example'));
+    $this->drupalLogin($this->priviledgedUser);
+  }
+  
+  /**
+   * t() no longer returns a string, but is used heavily in this test in contexts where it
+   * is important that it really return a string, and not TranslatableMarkup.  We substitute
+   * our own implementation that will hopefully be localizable, but will not have this problem.
+   */
+  protected function t($string, $args = [], $options = []) {
+    return (string)t($string, $args, $options);
+  }
+
+  /**
+   * Test the basic File Example UI.
+   *
+   * - Create a directory to work with
+   * - Foreach scheme create and read files using each of the three methods.
+   */
+  public function testFileExampleBasic() {
+
+    $expected_text = array(
+      $this->t('Write managed file') => $this->t('Saved managed file'),
+      $this->t('Write unmanaged file') => $this->t('Saved file as'),
+      $this->t('Unmanaged using PHP') => $this->t('Saved file as'),
+    );
+    // For each of the three buttons == three write types.
+    $buttons = array(
+      $this->t('Write managed file'),
+      $this->t('Write unmanaged file'),
+      $this->t('Unmanaged using PHP'),
+    );
+    foreach ($buttons as $button) {
+      // For each scheme supported by Drupal + the session:// wrapper.
+      $schemes = array('public', 'private', 'temporary', 'session');
+      foreach ($schemes as $scheme) {
+        // Create a directory for use.
+        $dirname = $scheme . '://' . $this->randomMachineName(10);
+
+        // Directory does not yet exist; assert that.
+        $edit = array(
+          'directory_name' => $dirname,
+        );
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), 'Verify that directory does not exist.');
+
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Create directory'));
+        $this->assertRaw(t('Directory %dirname is ready for use', array('%dirname' => $dirname)));
+
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname exists', array('%dirname' => $dirname)), 'Verify that directory now does exist.');
+
+        // Create a file in the directory we created.
+        $content = $this->randomString(30);
+        $filename = $dirname . '/' . $this->randomMachineName(30) . '.txt';
+
+        // Assert that the file we're about to create does not yet exist.
+        $edit = array(
+          'fileops_file' => $filename,
+        );
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), 'Verify that file does not yet exist.');
+
+        debug(
+          $this->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->drupalPostForm('examples/file_example/fileapi', $edit, $button);
+        debug($expected_text[$button], "Button Text");
+        $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->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename exists', array('%filename' => $filename)), '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->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Read the file and store it locally'));
+
+        $this->assertText(t('The file was read and copied'));
+
+        $edit = array(
+          'fileops_file' => $filename,
+        );
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Delete file'));
+        $this->assertText(t('Successfully deleted'));
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if file exists'));
+        $this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), 'Verify file has been deleted.');
+
+        $edit = array(
+          'directory_name' => $dirname,
+        );
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Delete directory'));
+        $this->drupalPostForm('examples/file_example/fileapi', $edit, $this->t('Check to see if directory exists'));
+        $this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), 'Verify that directory does not exist after deletion.');
+      }
+    }
+  }
+}
