My favorites | Sign in
Project Logo
                
Search
for
Updated Jul 20, 2009 by evertpot
Labels: Phase-Implementation, Featured
VirtualFilesystems  
Explains how to create custom filesystems

Overview

SabreDAV is built to easily adapt existing business logic onto a virtual network filesystem. This document explores how this can be setup.

High-level API

SabreDAV is shipped with an API that should ease creating directory-tree structures. The SabreDAV library is built with the following 3 layers:

Server layer

The Server class deals with the inner workings of the protocol. It maps all the low-level HTTP operations to a Tree class.

Tree layer

The Tree class is responsible for returning File and Directory objects based on a path. As a starting point it can be easier to just always use the ObjectTree object, which has all the functionality already implemented.

It can be benefitial to roll your own if your architecture allows optmizations in requesting objects or copying files.

File/Directory layer

This manual will focus on implementing a virtual filesystem on this layer.

Files and Directories

Files and Directories both implement the Sabre_DAV_INode interface, this interface dictates the following methods should be implemented:

Additionally File objects need to implement the following methods:

Directory objects add the following:

Inheritance tree

Note: this tree is slightly simplified for clarification

Sabre_DAV_INode (base interface for all nodes in a tree)
 +-Sabre_DAV_IFile (base interface for all files)
 |  +-Sabre_DAV_File (base helper class)
 |
 +-Sabre_DAV_DIrectory (base interface for all directories)
    +-Sabre_DAV_Directory (base helper class)
    

Next to the interfaces, there are two helper classes in this diagram (Sabre_DAV_File and Sabre_DAV_Directory). These classes are an easy starting point, as they will lock down most operations by default (by reporting 'permission denied'), so we can start with a read-only filesystem.

Implementation

Our read-only filesystem is going to be based off the standard server filesystem. Normally it would be better to use something like the apache module mod_dav for this, but it will allow us to easily explain the concepts.

Getting the classes ready

For this demonstration we need to create 2 classes, one for a directory and one for a file. We'll start out with the Directory class

class MyDirectory extends Sabre_DAV_Directory {

  private $myPath;

  function __construct($myPath) {

    $this->myPath = $myPath;

  }

  function getChildren() {

    $children = array();
    // Loop through the directory, and create objects for each node
    foreach(scandir($this->myPath) as $node) {

      // Ignoring files staring with .
      if ($node[0]==='.') continue;
      $children[] = $this->getChild($node);
        
    }

    return $children;

  }

  function getChild($name) {

      $path = $this->myPath . '/' . $name;

      // We have to throw a FileNotFound exception if the file didn't exist
      if (!file_exists($this->myPath)) throw new Sabre_DAV_Exception_FileNotFound('The file with name: ' . $name . ' could not be found');
      // Some added security

      if ($name[0]=='.')  throw new Sabre_DAV_Exception_FileNotFound('Access denied');

      if (is_dir($path)) {

          return new MyDirectory($path);

      } else {

          return new MyFile($path);

      }

  }

  function getName() {

      return basename($this->myPath);

  }

}

In the example is shown the absolute minimum of methods that need to be implemented in order to create a read-only directory. I'm hoping the code will speak for itself.

Same goes for the MyFile class

class MyFile extends Sabre_DAV_File {

  private $myPath;

  function __construct($myPath) {

    $this->myPath = $myPath;

  }

  function getName() {

      return basename($this->myPath);

  }

  function get() {

    return fopen($this->myPath,'r');

  }

  function getSize() {

      return filesize($this->myPath);

  }

}

It's important thing to note is, that you should usually not pass strings around. Although the get() method can just return a string, especially with larger files it's recommended to use streams (as shown with fopen). The put() and createFile() methods will always get a readable stream resource as arguments.

Setting up

I'm explaining the usage of your newly created server through code comments

// Make sure there is a directory in your current directory named 'public'. We will be exposing that directory to WebDAV
$publicDir = new MyDirectory('public');

// Now we create an ObjectTree, which dispatches all requests to your newly created file system
$objectTree = new Sabre_DAV_ObjectTree($publicDir);

// The object tree needs in turn to be passed to the server class
$server = new Sabre_DAV_Server($objectTree);

// We're required to set the base uri, it is recommended to put your webdav server on a root of a domain
$server->setBaseUri('/');

// And off we go!
$server->exec();

This is not virtual

Thats right! This is where you come in. You can make your MyFile and MyDirectory classes completely independent from the actual underlying filesystem. The list of items returned from getChildren could be a list of blogposts, and the 'get' method could return html data.

Write support

In order to get writing/modification support you should implement all the remaining methods. A good example of a completely built-out system like this can be found in the Sabre_DAV_FS directory. This system should closely mimic apache's mod_dav. Implementation of these is up to you (and optional) and is not written out in this manual, because at this point this should be fairly simple.

However, this is not enough. OS/X Finder and DavFS will demand you add locking support to your filesystem.

Locking support

Locking helps insuring no 2 people can overwrite each others changes. WebDAV has a system to accomodate locking. The simplest way to implement locking, is to use the Locks Plugin. Simply attach the Lock Manager to your Object Tree class, and you're off. The last example is extended to add a lock manager.

// Make sure there is a directory in your current directory named 'public'. We will be exposing that directory to WebDAV
$publicDir = new MyDirectory('public');

// Now we create an ObjectTree, which dispatches all requests to your newly created file system
$objectTree = new Sabre_DAV_ObjectTree($publicDir);
$objectTree->setLockManager($lockManager);

// The object tree needs in turn to be passed to the server class
$server = new Sabre_DAV_Server($objectTree);

// We're required to set the base uri, it is recommended to put your webdav server on a root of a domain
$server->setBaseUri('/');

// Also make sure there is a 'data' directory, writable by the server. This directory is used to store information about locks
$lockManager = new Sabre_DAV_Locks_Backend_FS('data');
$lockPlugin = new Sabre_DAV_Locks_Plugin($lockManager);
$server->addPlugin($lockPlugin);

// And off we go!
$server->exec();

Comment by imnotjessie, May 27, 2009

The function MyDirectory?::getChild function is wrong when it returns directories, instead of passing the name, it should pass the path, e.g.:

      if (is_dir($path)) {

          return new MyDirectory($path);

      } else {

          return new MyFile($path);

      }
Comment by evertpot, May 27, 2009

Thanks sir, fixed.


Sign in to add a comment
Hosted by Google Code