File: ExternalStoreMwstore.php

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (134 lines) | stat: -rw-r--r-- 4,672 bytes parent folder | download | duplicates (2)
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
<?php
/**
 * 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
 */

use MediaWiki\FileBackend\FileBackendGroup;
use MediaWiki\MediaWikiServices;
use MediaWiki\WikiMap\WikiMap;
use Wikimedia\FileBackend\FileBackend;
use Wikimedia\FileBackend\FSFileBackend;

/**
 * External storage in a FileBackend.
 *
 * In this system, each store "location" maps to the name of a file backend.
 * The file backends must be defined in $wgFileBackends and must be global
 * and fully qualified with a global "wikiId" prefix in the configuration.
 *
 * @see ExternalStoreAccess
 * @ingroup ExternalStorage
 * @since 1.21
 */
class ExternalStoreMwstore extends ExternalStoreMedium {
	/** @var FileBackendGroup */
	private $fbGroup;

	/**
	 * @see ExternalStoreMedium::__construct()
	 * @param array $params Additional parameters include:
	 *   - fbGroup: a FileBackendGroup instance
	 */
	public function __construct( array $params ) {
		parent::__construct( $params );
		if ( !isset( $params['fbGroup'] ) || !( $params['fbGroup'] instanceof FileBackendGroup ) ) {
			throw new InvalidArgumentException( "FileBackendGroup required in 'fbGroup' field." );
		}
		$this->fbGroup = $params['fbGroup'];
	}

	/**
	 * Fetch data from a given external store URL
	 *
	 * @see ExternalStoreMedium::fetchFromURL()
	 * @param string $url An external store URL in the form of mwstore://backend/container/wiki/id
	 * @return string|bool
	 */
	public function fetchFromURL( $url ) {
		$be = $this->fbGroup->backendFromPath( $url );
		if ( $be instanceof FileBackend ) {
			// We don't need "latest" since objects are immutable and
			// backends should at least have "read-after-create" consistency.
			return $be->getFileContents( [ 'src' => $url ] );
		}

		return false;
	}

	/**
	 * Fetch data from given external store URLs.
	 * The URLs are in the form of mwstore://backend/container/wiki/id
	 *
	 * @param array $urls An array of external store URLs
	 * @return array A map from url to stored content. Failed results are not represented.
	 */
	public function batchFetchFromURLs( array $urls ) {
		$pathsByBackend = [];
		foreach ( $urls as $url ) {
			$be = $this->fbGroup->backendFromPath( $url );
			if ( $be instanceof FileBackend ) {
				$pathsByBackend[$be->getName()][] = $url;
			}
		}
		$blobs = [];
		foreach ( $pathsByBackend as $backendName => $paths ) {
			$be = $this->fbGroup->get( $backendName );
			$blobs += $be->getFileContentsMulti( [ 'srcs' => $paths ] );
		}

		return $blobs;
	}

	public function store( $backend, $data ) {
		$be = $this->fbGroup->get( $backend );
		// Get three random base 36 characters to act as shard directories
		$rand = Wikimedia\base_convert( (string)mt_rand( 0, 46655 ), 10, 36, 3 );
		// Make sure ID is roughly lexicographically increasing for performance
		$gen = MediaWikiServices::getInstance()->getGlobalIdGenerator();
		$id = str_pad( $gen->newTimestampedUID128( 32 ), 26, '0', STR_PAD_LEFT );
		// Segregate items by DB domain ID for the sake of bookkeeping
		$domain = $this->isDbDomainExplicit
			? $this->dbDomain
			// @FIXME: this does not include the schema for b/c but it ideally should
			: WikiMap::getWikiIdFromDbDomain( $this->dbDomain );
		$url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $domain );
		// Use directory/container sharding
		$url .= ( $be instanceof FSFileBackend )
			? "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}" // keep directories small
			: "/{$rand[0]}/{$rand[1]}/{$id}"; // container sharding is only 2-levels

		$be->prepare( [ 'dir' => dirname( $url ), 'noAccess' => 1, 'noListing' => 1 ] );
		$status = $be->create( [ 'dst' => $url, 'content' => $data ] );

		if ( $status->isOK() ) {
			return $url;
		}

		throw new ExternalStoreException( __METHOD__ . ": operation failed: $status" );
	}

	public function isReadOnly( $backend ) {
		if ( parent::isReadOnly( $backend ) ) {
			return true;
		}

		$be = $this->fbGroup->get( $backend );

		return $be->isReadOnly();
	}
}