<commit_before><?
/*
    ***** BEGIN LICENSE BLOCK *****
    
    This file is part of the Zotero Data Server.
    
    Copyright © 2010 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 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 Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    ***** END LICENSE BLOCK *****
*/

class Zotero_Shards {
	private static $libraryShards = array();
	private static $shardInfo = array();
	
	public static function getShardInfo($shardID) {
		if (!$shardID) {
			throw new Exception('$shardID not provided');
		}
		
		if (isset(self::$shardInfo[$shardID])) {
			return self::$shardInfo[$shardID];
		}
		
		//$cacheKey = 'shardInfo_' . $shardID;
		//$shardInfo = Z_Core::$MC->get($cacheKey);
		//if ($shardInfo) {
		//	self::$shardInfo[$shardID] = $shardInfo;
		//	return $shardInfo;
		//}
		
		$sql = "SELECT address, port, username, password, db, `ssl`,
				CASE
					WHEN shardHosts.state='up' THEN shards.state
					WHEN shardHosts.state='readonly' THEN
						IF(shards.state='down', 'down', 'readonly')
					WHEN shardHosts.state='down' THEN 'down'
				END AS state
				FROM shards JOIN shardHosts USING (shardHostID) WHERE shardID=?";
		$shardInfo = Zotero_DB::rowQuery($sql, $shardID);
		if (!$shardInfo) {
			throw new Exception("Shard $shardID not found");
		}
		
		self::$shardInfo[$shardID] = $shardInfo;
		//Z_Core::$MC->set($cacheKey, $shardInfo);
		
		return $shardInfo;
	}
	
	
	public static function shardIsWriteable($shardID) {
		$shardInfo = self::getShardInfo($shardID);
		return $shardInfo['state'] == 'up';
	}
	
	
	public static function getByLibraryID($libraryID) {
		if (!$libraryID) {
			throw new Exception('$libraryID not provided');
		}
		
		if (isset(self::$libraryShards[$libraryID])) {
			return self::$libraryShards[$libraryID];
		}
		
		$cacheKey = 'libraryShard_' . $libraryID;
		$shardID = Z_Core::$MC->get($cacheKey);
		if ($shardID) {
			self::$libraryShards[$libraryID] = $shardID;
			return $shardID;
		}
		
		$sql = "SELECT shardID FROM libraries WHERE libraryID=?";
		$shardID = Zotero_DB::valueQuery($sql, $libraryID);
		if (!$shardID) {
			throw new Exception("Shard not found for library $libraryID");
		}
		
		self::$libraryShards[$libraryID] = $shardID;
		Z_Core::$MC->set($cacheKey, $shardID);
		
		return $shardID;
	}
	
	
	public static function getByUserID($userID) {
		$libraryID = Zotero_Users::getLibraryIDFromUserID($userID);
		return self::getByLibraryID($libraryID);
	}
	
	
	public static function getByGroupID($groupID) {
		$libraryID = Zotero_Groups::getLibraryIDFromGroupID($groupID);
		return self::getByLibraryID($libraryID);
	}
	
	
	/**
	 * Returns shardIDs of all shards storing libraries this user belongs to
	 */
	public static function getUserShards($userID) {
		return array_unique(
			array_merge(
				array(self::getByUserID($userID)),
				Zotero_Groups::getUserGroupShards($userID)
			)
		);
	}
	
	
	public static function getNextShard() {
		// TODO: figure out best shard
		return 2;
	}
	
	
	private static function setShard($libraryID, $newShardID) {
		$currentShardID = self::getByLibraryID($libraryID);
		if ($currentShardID == $newShardID) {
			throw new Exception("Library $libraryID is already on shard $newShardID");
		}
		
		$cacheKey = 'libraryShard_' . $libraryID;
		Z_Core::$MC->delete($cacheKey);
		
		$sql = "UPDATE libraries SET shardID=? WHERE libraryID=?";
		Zotero_DB::query($sql, array($newShardID, $libraryID));
	}
	
	
	public static function moveLibrary($libraryID, $newShardID) {
		$currentShardID = self::getByLibraryID($libraryID);
		if ($currentShardID == $newShardID) {
			throw new Exception("Library $libraryID is already on shard $newShardID");
		}
		if (!self::shardIsWriteable($newShardID)) {
			throw new Exception("Shard $newShardID is not writeable");
		}
		
		if (Zotero_Libraries::isLocked($libraryID)) {
			throw new Exception("Library $libraryID is locked");
		}
		
		// Make sure there's no stale data on the new shard
		if (self::checkForLibrary($libraryID, $newShardID)) {
			throw new Exception("Library $libraryID data already exists on shard $newShardID");
		}
		
		Zotero_DB::beginTransaction();
		
		Zotero_DB::query("SET foreign_key_checks=0", false, $newShardID);
		
		$tables = array(
			'collections',
			'creators',
			'items',
			'relations',
			'savedSearches',
			'tags',
			'collectionItems',
			'deletedItems',
			'groupItems',
			'itemAttachments',
			'itemCreators',
			'itemData',
			'itemNotes',
			'itemRelated',
			'itemTags',
			'savedSearchConditions',
			'shardLibraries',
			'storageFileItems',
			'syncDeleteLogIDs',<commit_msg>moveShard() fix<commit_after><?
/*
    ***** BEGIN LICENSE BLOCK *****
    
    This file is part of the Zotero Data Server.
    
    Copyright © 2010 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 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 Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    ***** END LICENSE BLOCK *****
*/

class Zotero_Shards {
	private static $libraryShards = array();
	private static $shardInfo = array();
	
	public static function getShardInfo($shardID) {
		if (!$shardID) {
			throw new Exception('$shardID not provided');
		}
		
		if (isset(self::$shardInfo[$shardID])) {
			return self::$shardInfo[$shardID];
		}
		
		//$cacheKey = 'shardInfo_' . $shardID;
		//$shardInfo = Z_Core::$MC->get($cacheKey);
		//if ($shardInfo) {
		//	self::$shardInfo[$shardID] = $shardInfo;
		//	return $shardInfo;
		//}
		
		$sql = "SELECT address, port, username, password, db, `ssl`,
				CASE
					WHEN shardHosts.state='up' THEN shards.state
					WHEN shardHosts.state='readonly' THEN
						IF(shards.state='down', 'down', 'readonly')
					WHEN shardHosts.state='down' THEN 'down'
				END AS state
				FROM shards JOIN shardHosts USING (shardHostID) WHERE shardID=?";
		$shardInfo = Zotero_DB::rowQuery($sql, $shardID);
		if (!$shardInfo) {
			throw new Exception("Shard $shardID not found");
		}
		
		self::$shardInfo[$shardID] = $shardInfo;
		//Z_Core::$MC->set($cacheKey, $shardInfo);
		
		return $shardInfo;
	}
	
	
	public static function shardIsWriteable($shardID) {
		$shardInfo = self::getShardInfo($shardID);
		return $shardInfo['state'] == 'up';
	}
	
	
	public static function getByLibraryID($libraryID) {
		if (!$libraryID) {
			throw new Exception('$libraryID not provided');
		}
		
		if (isset(self::$libraryShards[$libraryID])) {
			return self::$libraryShards[$libraryID];
		}
		
		$cacheKey = 'libraryShard_' . $libraryID;
		$shardID = Z_Core::$MC->get($cacheKey);
		if ($shardID) {
			self::$libraryShards[$libraryID] = $shardID;
			return $shardID;
		}
		
		$sql = "SELECT shardID FROM libraries WHERE libraryID=?";
		$shardID = Zotero_DB::valueQuery($sql, $libraryID);
		if (!$shardID) {
			throw new Exception("Shard not found for library $libraryID");
		}
		
		self::$libraryShards[$libraryID] = $shardID;
		Z_Core::$MC->set($cacheKey, $shardID);
		
		return $shardID;
	}
	
	
	public static function getByUserID($userID) {
		$libraryID = Zotero_Users::getLibraryIDFromUserID($userID);
		return self::getByLibraryID($libraryID);
	}
	
	
	public static function getByGroupID($groupID) {
		$libraryID = Zotero_Groups::getLibraryIDFromGroupID($groupID);
		return self::getByLibraryID($libraryID);
	}
	
	
	/**
	 * Returns shardIDs of all shards storing libraries this user belongs to
	 */
	public static function getUserShards($userID) {
		return array_unique(
			array_merge(
				array(self::getByUserID($userID)),
				Zotero_Groups::getUserGroupShards($userID)
			)
		);
	}
	
	
	public static function getNextShard() {
		// TODO: figure out best shard
		return 2;
	}
	
	
	private static function setShard($libraryID, $newShardID) {
		$currentShardID = self::getByLibraryID($libraryID);
		if ($currentShardID == $newShardID) {
			throw new Exception("Library $libraryID is already on shard $newShardID");
		}
		
		$cacheKey = 'libraryShard_' . $libraryID;
		Z_Core::$MC->delete($cacheKey);
		
		$sql = "UPDATE libraries SET shardID=? WHERE libraryID=?";
		Zotero_DB::query($sql, array($newShardID, $libraryID));
	}
	
	
	public static function moveLibrary($libraryID, $newShardID) {
		$currentShardID = self::getByLibraryID($libraryID);
		if ($currentShardID == $newShardID) {
			throw new Exception("Library $libraryID is already on shard $newShardID");
		}
		if (!self::shardIsWriteable($newShardID)) {
			throw new Exception("Shard $newShardID is not writeable");
		}
		
		if (Zotero_Libraries::isLocked($libraryID)) {
			throw new Exception("Library $libraryID is locked");
		}
		
		// Make sure there's no stale data on the new shard
		if (self::checkForLibrary($libraryID, $newShardID)) {
			throw new Exception("Library $libraryID data already exists on shard $newShardID");
		}
		
		Zotero_DB::beginTransaction();
		
		Zotero_DB::query("SET foreign_key_checks=0", false, $newShardID);
		
		$tables = array(
			'shardLibraries',
			'collections',
			'creators',
			'items',
			'relations',
			'savedSearches',
			'tags',
			'collectionItems',
			'deletedItems',
			'groupItems',
			'itemAttachments',
			'itemCreators',
			'itemData',
			'itemNotes',
			'itemRelated',
			'itemTags',
			'savedSearchConditions',
			'storageFileItems',
			'syncDeleteLogIDs',
{'fraction_non_alphanumeric': 0.13025369178341537, 'fraction_numerical': 0.0013252555850056798, 'mean_word_length': 3.8374542124542126, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4973784', 'n_tokens_mistral': 3957, 'n_tokens_neox': 3460, 'n_words': 894}