From 4ea7cbb0f5d97b0ae5fe8a6c3c43718d3fa5172e Mon Sep 17 00:00:00 2001
From: Robin Appelman <icewind@owncloud.com>
Date: Wed, 15 Jul 2015 16:14:32 +0200
Subject: [PATCH] Add database backend for high level locking

---
 db_structure.xml                       |  39 ++++++++
 lib/private/lock/dblockingprovider.php | 131 +++++++++++++++++++++++++
 lib/repair/dropoldtables.php           |   1 -
 tests/lib/lock/dblockingprovider.php   |  43 ++++++++
 version.php                            |   2 +-
 5 files changed, 214 insertions(+), 2 deletions(-)
 create mode 100644 lib/private/lock/dblockingprovider.php
 create mode 100644 tests/lib/lock/dblockingprovider.php

diff --git a/db_structure.xml b/db_structure.xml
index 6d1cf6973c..95acefcfae 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -1180,5 +1180,44 @@
 
 	</table>
 
+	<table>
+
+		<!--
+		Table for storing high-level locking
+		-->
+		<name>*dbprefix*locks</name>
+
+		<declaration>
+
+			<field>
+				<name>lock</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<length>4</length>
+			</field>
+
+			<field>
+				<name>path</name>
+				<type>text</type>
+				<notnull>true</notnull>
+				<length>64</length>
+			</field>
+
+
+			<index>
+				<primary>true</primary>
+				<unique>true</unique>
+				<name>lock_path_index</name>
+				<field>
+					<name>path</name>
+					<sorting>ascending</sorting>
+				</field>
+			</index>
+
+		</declaration>
+
+	</table>
+
 
 </database>
diff --git a/lib/private/lock/dblockingprovider.php b/lib/private/lock/dblockingprovider.php
new file mode 100644
index 0000000000..70f4539eb2
--- /dev/null
+++ b/lib/private/lock/dblockingprovider.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Lock;
+
+use OCP\IDBConnection;
+use OCP\Lock\LockedException;
+
+class DBLockingProvider extends AbstractLockingProvider {
+	/**
+	 * @var \OCP\IDBConnection
+	 */
+	private $connection;
+
+	/**
+	 * @param \OCP\IDBConnection $connection
+	 */
+	public function __construct(IDBConnection $connection) {
+		$this->connection = $connection;
+	}
+
+	protected function initLockField($path) {
+		$this->connection->insertIfNotExist('*PREFIX*locks', ['path' => $path, 'lock' => 0], ['path']);
+	}
+
+	/**
+	 * @param string $path
+	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
+	 * @return bool
+	 */
+	public function isLocked($path, $type) {
+		$query = $this->connection->prepare('SELECT `lock` from `*PREFIX*locks` WHERE `path` = ?');
+		$query->execute([$path]);
+		$lockValue = (int)$query->fetchColumn();
+		if ($type === self::LOCK_SHARED) {
+			return $lockValue > 0;
+		} else if ($type === self::LOCK_EXCLUSIVE) {
+			return $lockValue === -1;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * @param string $path
+	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
+	 * @throws \OCP\Lock\LockedException
+	 */
+	public function acquireLock($path, $type) {
+		$this->initLockField($path);
+		if ($type === self::LOCK_SHARED) {
+			$result = $this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = `lock` + 1 WHERE `path` = ? AND `lock` >= 0',
+				[$path]
+			);
+		} else {
+			$result = $this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = -1 WHERE `path` = ? AND `lock` = 0',
+				[$path]
+			);
+		}
+		if ($result !== 1) {
+			throw new LockedException($path);
+		}
+		$this->markAcquire($path, $type);
+	}
+
+	/**
+	 * @param string $path
+	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
+	 */
+	public function releaseLock($path, $type) {
+		$this->initLockField($path);
+		if ($type === self::LOCK_SHARED) {
+			$this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = `lock` - 1 WHERE `path` = ? AND `lock` > 0',
+				[$path]
+			);
+		} else {
+			$this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = 0 WHERE `path` = ? AND `lock` = -1',
+				[$path]
+			);
+		}
+		$this->markRelease($path, $type);
+	}
+
+	/**
+	 * Change the type of an existing lock
+	 *
+	 * @param string $path
+	 * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
+	 * @throws \OCP\Lock\LockedException
+	 */
+	public function changeLock($path, $targetType) {
+		$this->initLockField($path);
+		if ($targetType === self::LOCK_SHARED) {
+			$result = $this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = 1 WHERE `path` = ? AND `lock` = -1',
+				[$path]
+			);
+		} else {
+			$result = $this->connection->executeUpdate(
+				'UPDATE `*PREFIX*locks` SET `lock` = -1 WHERE `path` = ? AND `lock` = 1',
+				[$path]
+			);
+		}
+		if ($result !== 1) {
+			throw new LockedException($path);
+		}
+		$this->markChange($path, $targetType);
+	}
+}
diff --git a/lib/repair/dropoldtables.php b/lib/repair/dropoldtables.php
index cfe0df6cb5..89f872e16c 100644
--- a/lib/repair/dropoldtables.php
+++ b/lib/repair/dropoldtables.php
@@ -76,7 +76,6 @@ class DropOldTables extends BasicEmitter implements RepairStep {
 			'calendar_share_event',
 			'foldersize',
 			'fscache',
-			'locks',
 			'log',
 			'media_albums',
 			'media_artists',
diff --git a/tests/lib/lock/dblockingprovider.php b/tests/lib/lock/dblockingprovider.php
new file mode 100644
index 0000000000..4229ffb9c5
--- /dev/null
+++ b/tests/lib/lock/dblockingprovider.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace Test\Lock;
+
+class DBLockingProvider extends LockingProvider {
+
+	/**
+	 * @var \OCP\IDBConnection
+	 */
+	private $connection;
+
+	/**
+	 * @return \OCP\Lock\ILockingProvider
+	 */
+	protected function getInstance() {
+		$this->connection = \OC::$server->getDatabaseConnection();
+		return new \OC\Lock\DBLockingProvider($this->connection);
+	}
+
+	public function tearDown() {
+		$this->connection->executeQuery('DELETE FROM `*PREFIX*locks`');
+		parent::tearDown();
+	}
+}
diff --git a/version.php b/version.php
index 7ccd2e6b54..a115f4b26b 100644
--- a/version.php
+++ b/version.php
@@ -22,7 +22,7 @@
 // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
 // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
 // when updating major/minor version number.
-$OC_Version=array(8, 2, 0, 3);
+$OC_Version=array(8, 2, 0, 4);
 
 // The human readable string
 $OC_VersionString='8.2 pre alpha';
-- 
GitLab