summaryrefslogtreecommitdiff
path: root/includes/filebackend/filejournal/FileJournal.php
blob: a1b7a4596183e6a7fdeaac193a5109ec6ab20f0c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<?php
/**
 * @defgroup FileJournal File journal
 * @ingroup FileBackend
 */

/**
 * File operation journaling.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @ingroup FileJournal
 * @author Aaron Schulz
 */

/**
 * @brief Class for handling file operation journaling.
 *
 * Subclasses should avoid throwing exceptions at all costs.
 *
 * @ingroup FileJournal
 * @since 1.20
 */
abstract class FileJournal {
	protected $backend; // string
	protected $ttlDays; // integer

	/**
	 * Construct a new instance from configuration.
	 * $config includes:
	 *     'ttlDays' : days to keep log entries around (false means "forever")
	 *
	 * @param $config Array
	 */
	protected function __construct( array $config ) {
		$this->ttlDays = isset( $config['ttlDays'] ) ? $config['ttlDays'] : false;
	}

	/**
	 * Create an appropriate FileJournal object from config
	 *
	 * @param $config Array
	 * @param string $backend A registered file backend name
	 * @throws MWException
	 * @return FileJournal
	 */
	final public static function factory( array $config, $backend ) {
		$class = $config['class'];
		$jrn = new $class( $config );
		if ( !$jrn instanceof self ) {
			throw new MWException( "Class given is not an instance of FileJournal." );
		}
		$jrn->backend = $backend;
		return $jrn;
	}

	/**
	 * Get a statistically unique ID string
	 *
	 * @return string <9 char TS_MW timestamp in base 36><22 random base 36 chars>
	 */
	final public function getTimestampedUUID() {
		$s = '';
		for ( $i = 0; $i < 5; $i++ ) {
			$s .= mt_rand( 0, 2147483647 );
		}
		$s = wfBaseConvert( sha1( $s ), 16, 36, 31 );
		return substr( wfBaseConvert( wfTimestamp( TS_MW ), 10, 36, 9 ) . $s, 0, 31 );
	}

	/**
	 * Log changes made by a batch file operation.
	 * $entries is an array of log entries, each of which contains:
	 *     op      : Basic operation name (create, update, delete)
	 *     path    : The storage path of the file
	 *     newSha1 : The final base 36 SHA-1 of the file
	 * Note that 'false' should be used as the SHA-1 for non-existing files.
	 *
	 * @param array $entries List of file operations (each an array of parameters)
	 * @param string $batchId UUID string that identifies the operation batch
	 * @return Status
	 */
	final public function logChangeBatch( array $entries, $batchId ) {
		if ( !count( $entries ) ) {
			return Status::newGood();
		}
		return $this->doLogChangeBatch( $entries, $batchId );
	}

	/**
	 * @see FileJournal::logChangeBatch()
	 *
	 * @param array $entries List of file operations (each an array of parameters)
	 * @param string $batchId UUID string that identifies the operation batch
	 * @return Status
	 */
	abstract protected function doLogChangeBatch( array $entries, $batchId );

	/**
	 * Get the position ID of the latest journal entry
	 *
	 * @return integer|false
	 */
	final public function getCurrentPosition() {
		return $this->doGetCurrentPosition();
	}

	/**
	 * @see FileJournal::getCurrentPosition()
	 * @return integer|false
	 */
	abstract protected function doGetCurrentPosition();

	/**
	 * Get the position ID of the latest journal entry at some point in time
	 *
	 * @param $time integer|string timestamp
	 * @return integer|false
	 */
	final public function getPositionAtTime( $time ) {
		return $this->doGetPositionAtTime( $time );
	}

	/**
	 * @see FileJournal::getPositionAtTime()
	 * @param $time integer|string timestamp
	 * @return integer|false
	 */
	abstract protected function doGetPositionAtTime( $time );

	/**
	 * Get an array of file change log entries.
	 * A starting change ID and/or limit can be specified.
	 *
	 * The result as a list of associative arrays, each having:
	 *     id         : unique, monotonic, ID for this change
	 *     batch_uuid : UUID for an operation batch
	 *     backend    : the backend name
	 *     op         : primitive operation (create,update,delete,null)
	 *     path       : affected storage path
	 *     new_sha1   : base 36 sha1 of the new file had the operation succeeded
	 *     timestamp  : TS_MW timestamp of the batch change

	 * Also, $next is updated to the ID of the next entry.
	 *
	 * @param $start integer Starting change ID or null
	 * @param $limit integer Maximum number of items to return
	 * @param &$next string
	 * @return Array
	 */
	final public function getChangeEntries( $start = null, $limit = 0, &$next = null ) {
		$entries = $this->doGetChangeEntries( $start, $limit ? $limit + 1 : 0 );
		if ( $limit && count( $entries ) > $limit ) {
			$last = array_pop( $entries ); // remove the extra entry
			$next = $last['id']; // update for next call
		} else {
			$next = null; // end of list
		}
		return $entries;
	}

	/**
	 * @see FileJournal::getChangeEntries()
	 * @return Array
	 */
	abstract protected function doGetChangeEntries( $start, $limit );

	/**
	 * Purge any old log entries
	 *
	 * @return Status
	 */
	final public function purgeOldLogs() {
		return $this->doPurgeOldLogs();
	}

	/**
	 * @see FileJournal::purgeOldLogs()
	 * @return Status
	 */
	abstract protected function doPurgeOldLogs();
}

/**
 * Simple version of FileJournal that does nothing
 * @since 1.20
 */
class NullFileJournal extends FileJournal {
	/**
	 * @see FileJournal::doLogChangeBatch()
	 * @param $entries array
	 * @param $batchId string
	 * @return Status
	 */
	protected function doLogChangeBatch( array $entries, $batchId ) {
		return Status::newGood();
	}

	/**
	 * @see FileJournal::doGetCurrentPosition()
	 * @return integer|false
	 */
	protected function doGetCurrentPosition() {
		return false;
	}

	/**
	 * @see FileJournal::doGetPositionAtTime()
	 * @param $time integer|string timestamp
	 * @return integer|false
	 */
	protected function doGetPositionAtTime( $time ) {
		return false;
	}

	/**
	 * @see FileJournal::doGetChangeEntries()
	 * @return Array
	 */
	protected function doGetChangeEntries( $start, $limit ) {
		return array();
	}

	/**
	 * @see FileJournal::doPurgeOldLogs()
	 * @return Status
	 */
	protected function doPurgeOldLogs() {
		return Status::newGood();
	}
}