From cdd9ffc8a5dba2d4c3d83d77fc7806fd017a6b97 Mon Sep 17 00:00:00 2001
From: Bart Visscher <bartv@thisnet.nl>
Date: Fri, 20 Jul 2012 23:52:47 +0200
Subject: [PATCH] Add ETag support to the Sabre file connector.

This is based on the md5 of the file, can be changed later
---
 lib/connector/sabre/directory.php | 22 +++++++++++++++++++---
 lib/connector/sabre/file.php      | 25 +++++++++++++++++++++----
 lib/connector/sabre/node.php      | 26 ++++++++++++++++++++++++++
 lib/filesystem.php                |  8 ++++++++
 4 files changed, 74 insertions(+), 7 deletions(-)

diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php
index f3f6541a8d4..0842fc4fc65 100644
--- a/lib/connector/sabre/directory.php
+++ b/lib/connector/sabre/directory.php
@@ -26,17 +26,33 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa
 	/**
 	 * Creates a new file in the directory
 	 *
-	 * data is a readable stream resource
+	 * Data will either be supplied as a stream resource, or in certain cases
+	 * as a string. Keep in mind that you may have to support either.
+	 *
+	 * After succesful creation of the file, you may choose to return the ETag
+	 * of the new file here.
+	 *
+	 * The returned ETag must be surrounded by double-quotes (The quotes should
+	 * be part of the actual string).
+	 *
+	 * If you cannot accurately determine the ETag, you should not return it.
+	 * If you don't store the file exactly as-is (you're transforming it
+	 * somehow) you should also not return an ETag.
+	 *
+	 * This means that if a subsequent GET to this new file does not exactly
+	 * return the same contents of what was submitted here, you are strongly
+	 * recommended to omit the ETag.
 	 *
 	 * @param string $name Name of the file
-	 * @param resource $data Initial payload
-	 * @return void
+	 * @param resource|string $data Initial payload
+	 * @return null|string
 	 */
 	public function createFile($name, $data = null) {
 
 		$newPath = $this->path . '/' . $name;
 		OC_Filesystem::file_put_contents($newPath,$data);
 
+		return OC_Connector_Sabre_Node::getETagPropertyForFile($newPath);
 	}
 
 	/**
diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php
index 4700dbf8b89..80f0a0ab4d8 100644
--- a/lib/connector/sabre/file.php
+++ b/lib/connector/sabre/file.php
@@ -26,13 +26,28 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
 	/**
 	 * Updates the data
 	 *
+	 * The data argument is a readable stream resource.
+	 *
+	 * After a succesful put operation, you may choose to return an ETag. The
+	 * etag must always be surrounded by double-quotes. These quotes must
+	 * appear in the actual string you're returning.
+	 *
+	 * Clients may use the ETag from a PUT request to later on make sure that
+	 * when they update the file, the contents haven't changed in the mean
+	 * time.
+	 *
+	 * If you don't plan to store the file byte-by-byte, and you return a
+	 * different object on a subsequent GET you are strongly recommended to not
+	 * return an ETag, and just return null.
+	 *
 	 * @param resource $data
-	 * @return void
+	 * @return string|null
 	 */
 	public function put($data) {
 
 		OC_Filesystem::file_put_contents($this->path,$data);
 
+		return OC_Connector_Sabre_Node::getETagPropertyForFile($this->path);
 	}
 
 	/**
@@ -79,9 +94,11 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
 	 * @return mixed
 	 */
 	public function getETag() {
-
-		return null;
-
+		$properties = $this->getProperties(array(self::GETETAG_PROPERTYNAME));
+		if (isset($properties[self::GETETAG_PROPERTYNAME])) {
+			return $properties[self::GETETAG_PROPERTYNAME];
+		}
+		return $this->getETagPropertyForFile($this->path);
 	}
 
 	/**
diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php
index be315a0ffd9..5bb92922f8f 100644
--- a/lib/connector/sabre/node.php
+++ b/lib/connector/sabre/node.php
@@ -22,6 +22,7 @@
  */
 
 abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IProperties {
+	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
 
 	/**
 	 * The path to the current node
@@ -200,4 +201,29 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr
 		}
 		return $props;
 	}
+
+	/**
+	 * Returns the ETag surrounded by double-quotes for this path.
+	 * @param string $path Path of the file
+	 * @return string|null Returns null if the ETag can not effectively be determined
+	 */
+	static public function getETagPropertyForFile($path) {
+		$tag = OC_Filesystem::hash('md5', $path);
+		if (empty($tag)) {
+			return null;
+		}
+		$etag = '"'.$tag.'"';
+		$query = OC_DB::prepare( 'INSERT INTO *PREFIX*properties (userid,propertypath,propertyname,propertyvalue) VALUES(?,?,?,?)' );
+		$query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME, $etag ));
+		return $etag;
+	}
+
+	/**
+	 * Remove the ETag from the cache.
+	 * @param string $path Path of the file
+	 */
+	static public function removeETagPropertyForFile($path) {
+		$query = OC_DB::prepare( 'DELETE FROM *PREFIX*properties WHERE userid = ? AND propertypath = ? AND propertyname = ?' );
+		$query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME ));
+	}
 }
diff --git a/lib/filesystem.php b/lib/filesystem.php
index 148656be00f..67dff95a183 100644
--- a/lib/filesystem.php
+++ b/lib/filesystem.php
@@ -483,7 +483,15 @@ class OC_Filesystem{
 	static public function hasUpdated($path,$time){
 		return self::$defaultInstance->hasUpdated($path,$time);
 	}
+
+	static public function removeETagHook() {
+		$path=$params['path'];
+		OC_Connector_Sabre_Node::removeETagPropertyForFile($path);
+	}
 }
+OC_Hook::connect('OC_Filesystem','post_write', 'OC_Filesystem','removeETagHook');
+OC_Hook::connect('OC_Filesystem','post_delete','OC_Filesystem','removeETagHook');
+OC_Hook::connect('OC_Filesystem','post_rename','OC_Filesystem','removeETagHook');
 
 OC_Util::setupFS();
 require_once('filecache.php');
-- 
GitLab