1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This program is free software: you can redistribute it and/or modify |
5
|
|
|
* it under the terms of the GNU Lesser General Public License as published by |
6
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
7
|
|
|
* (at your option) any later version. |
8
|
|
|
* |
9
|
|
|
* This program is distributed in the hope that it will be useful, |
10
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12
|
|
|
* GNU Lesser General Public License for more details. |
13
|
|
|
* |
14
|
|
|
* You should have received a copy of the GNU Lesser General Public License |
15
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
namespace fkooman\RemoteStorage; |
19
|
|
|
|
20
|
|
|
use fkooman\RemoteStorage\Exception\MetadataStorageException; |
21
|
|
|
use PDO; |
22
|
|
|
|
23
|
|
|
class MetadataStorage |
24
|
|
|
{ |
25
|
|
|
/** @var PDO */ |
26
|
|
|
private $db; |
27
|
|
|
|
28
|
|
|
/** @var RandomInterface */ |
29
|
|
|
private $random; |
30
|
|
|
|
31
|
|
|
public function __construct(PDO $db, RandomInterface $random = null) |
32
|
|
|
{ |
33
|
|
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
34
|
|
|
// $db->query('PRAGMA foreign_keys = ON'); |
|
|
|
|
35
|
|
|
$this->db = $db; |
36
|
|
|
if (is_null($random)) { |
37
|
|
|
$random = new Random(); |
38
|
|
|
} |
39
|
|
|
$this->random = $random; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
public function getVersion(Path $p) |
43
|
|
|
{ |
44
|
|
|
$md = $this->getMetadata($p); |
45
|
|
|
|
46
|
|
|
return null !== $md ? $md['version'] : null; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
public function getContentType(Path $p) |
50
|
|
|
{ |
51
|
|
|
$md = $this->getMetadata($p); |
52
|
|
|
|
53
|
|
|
return null !== $md ? $md['content_type'] : null; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
public function updateFolder(Path $p) |
57
|
|
|
{ |
58
|
|
|
if (!$p->getIsFolder()) { |
59
|
|
|
throw new MetadataStorageException('not a folder'); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
return $this->updateDocument($p, null); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* We have a very weird version update method by including a sequence number |
67
|
|
|
* that makes it easy for tests to see if there is correct behavior, a sequence |
68
|
|
|
* number is not enough though as deleting a file would reset the sequence number and |
69
|
|
|
* thus make it possible to have files with different content to have the same |
70
|
|
|
* sequence number in the same location, but in order to check if all versions |
71
|
|
|
* are updated up to the root we have to do this this way... |
72
|
|
|
*/ |
73
|
|
|
public function updateDocument(Path $p, $contentType) |
74
|
|
|
{ |
75
|
|
|
$currentVersion = $this->getVersion($p); |
76
|
|
|
if (null === $currentVersion) { |
77
|
|
|
$newVersion = '1:'.$this->random->get(8); |
78
|
|
|
$stmt = $this->db->prepare( |
79
|
|
|
'INSERT INTO md (path, content_type, version) VALUES(:path, :content_type, :version)' |
80
|
|
|
); |
81
|
|
|
} else { |
82
|
|
|
$explodedData = explode(':', $currentVersion); |
83
|
|
|
$newVersion = sprintf('%d:%s', $explodedData[0] + 1, $this->random->get(8)); |
84
|
|
|
$stmt = $this->db->prepare( |
85
|
|
|
'UPDATE md SET version = :version, content_type = :content_type WHERE path = :path' |
86
|
|
|
); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
$stmt->bindValue(':path', $p->getPath(), PDO::PARAM_STR); |
90
|
|
|
$stmt->bindValue(':content_type', $contentType, PDO::PARAM_STR); |
91
|
|
|
$stmt->bindValue(':version', $newVersion, PDO::PARAM_STR); |
92
|
|
|
$stmt->execute(); |
93
|
|
|
|
94
|
|
|
if (1 !== $stmt->rowCount()) { |
95
|
|
|
throw new MetadataStorageException('unable to update node'); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
public function deleteNode(Path $p) |
100
|
|
|
{ |
101
|
|
|
$stmt = $this->db->prepare( |
102
|
|
|
'DELETE FROM md WHERE path = :path' |
103
|
|
|
); |
104
|
|
|
$stmt->bindValue(':path', $p->getPath(), PDO::PARAM_STR); |
105
|
|
|
$stmt->execute(); |
106
|
|
|
|
107
|
|
|
if (1 !== $stmt->rowCount()) { |
108
|
|
|
throw new MetadataStorageException('unable to delete node'); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
public static function createTableQueries() |
113
|
|
|
{ |
114
|
|
|
return [ |
115
|
|
|
'CREATE TABLE IF NOT EXISTS md ( |
116
|
|
|
path VARCHAR(255) NOT NULL, |
117
|
|
|
content_type VARCHAR(255) DEFAULT NULL, |
118
|
|
|
version VARCHAR(255) NOT NULL, |
119
|
|
|
UNIQUE (path) |
120
|
|
|
)', |
121
|
|
|
]; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
public function init() |
125
|
|
|
{ |
126
|
|
|
$queries = self::createTableQueries(); |
127
|
|
|
foreach ($queries as $q) { |
128
|
|
|
$this->db->query($q); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Get the version of the path which can be either a folder or document. |
134
|
|
|
* |
135
|
|
|
* @param $path The full path to the folder or document |
136
|
|
|
* @returns the version of the path, or null if path does not exist |
137
|
|
|
*/ |
138
|
|
|
private function getMetadata(Path $p) |
139
|
|
|
{ |
140
|
|
|
$stmt = $this->db->prepare( |
141
|
|
|
'SELECT version, content_type FROM md WHERE path = :path' |
142
|
|
|
); |
143
|
|
|
$stmt->bindValue(':path', $p->getPath(), PDO::PARAM_STR); |
144
|
|
|
$stmt->execute(); |
145
|
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC); |
146
|
|
|
if (false !== $result) { |
147
|
|
|
return $result; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return; |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.