kopia lustrzana https://github.com/nextcloud/social
				
				
				
			
		
			
				
	
	
		
			398 wiersze
		
	
	
		
			9.8 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			398 wiersze
		
	
	
		
			9.8 KiB
		
	
	
	
		
			PHP
		
	
	
<?php
 | 
						|
 | 
						|
declare(strict_types=1);
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Nextcloud - Social Support
 | 
						|
 *
 | 
						|
 * This file is licensed under the Affero General Public License version 3 or
 | 
						|
 * later. See the COPYING file.
 | 
						|
 *
 | 
						|
 * @author Maxence Lange <maxence@artificial-owl.com>
 | 
						|
 * @copyright 2018, Maxence Lange <maxence@artificial-owl.com>
 | 
						|
 * @license GNU AGPL version 3 or any later version
 | 
						|
 *
 | 
						|
 * 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/>.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
namespace OCA\Social\Command;
 | 
						|
 | 
						|
use OCA\Social\Tools\Traits\TArrayTools;
 | 
						|
use Exception;
 | 
						|
use OC\Core\Command\Base;
 | 
						|
use OCA\Social\Db\CoreRequestBuilder;
 | 
						|
use OCA\Social\Service\CheckService;
 | 
						|
use OCA\Social\Service\ConfigService;
 | 
						|
use OCA\Social\Service\MiscService;
 | 
						|
use OCP\DB\QueryBuilder\IParameter;
 | 
						|
use OCP\DB\QueryBuilder\IQueryBuilder;
 | 
						|
use OCP\IDBConnection;
 | 
						|
use Symfony\Component\Console\Input\InputInterface;
 | 
						|
use Symfony\Component\Console\Input\InputOption;
 | 
						|
use Symfony\Component\Console\Output\OutputInterface;
 | 
						|
use Symfony\Component\Console\Question\ConfirmationQuestion;
 | 
						|
 | 
						|
class MigrateAlpha3 extends Base {
 | 
						|
	use TArrayTools;
 | 
						|
 | 
						|
	private IDBConnection $dbConnection;
 | 
						|
	private CoreRequestBuilder $coreRequestBuilder;
 | 
						|
	private CheckService $checkService;
 | 
						|
	private ConfigService $configService;
 | 
						|
	private MiscService $miscService;
 | 
						|
	private array $done = [];
 | 
						|
	public array $tables = [
 | 
						|
		'social_a2_actions' => [
 | 
						|
			['id_prim'],
 | 
						|
			'social_3_action',
 | 
						|
			[
 | 
						|
				'actor_id_prim' => 'PRIM:actor_id',
 | 
						|
				'object_id_prim' => 'PRIM:object_id'
 | 
						|
			]
 | 
						|
		],
 | 
						|
		'social_a2_actors' => [['user_id'], 'social_3_actor', []],
 | 
						|
		'social_a2_cache_actors' => [['id_prim'], 'social_3_cache_actor', []],
 | 
						|
		'social_a2_cache_documts' => [['id_prim'], 'social_3_cache_doc', []],
 | 
						|
		'social_a2_follows' => [
 | 
						|
			['id_prim'],
 | 
						|
			'social_3_follow',
 | 
						|
			[
 | 
						|
				'actor_id_prim' => 'PRIM:actor_id',
 | 
						|
				'object_id_prim' => 'PRIM:object_id',
 | 
						|
				'follow_id_prim' => 'PRIM:follow_id'
 | 
						|
			]
 | 
						|
		],
 | 
						|
 | 
						|
		'social_a2_hashtags' => [['hashtag'], 'social_3_hashtag', []],
 | 
						|
		'social_a2_request_queue' => [['id'], 'social_3_req_queue', []],
 | 
						|
		'social_a2_stream' => [
 | 
						|
			['id_prim'],
 | 
						|
			'social_3_stream',
 | 
						|
			[
 | 
						|
				'object_id_prim' => 'PRIM:object_id',
 | 
						|
				'in_reply_to_prim' => 'PRIM:in_reply_to',
 | 
						|
				'attributed_to_prim' => 'PRIM:attributed_to',
 | 
						|
				'filter_duplicate' => 'COPY:hidden_on_timeline',
 | 
						|
				'hidden_on_timeline' => 'REMOVED:'
 | 
						|
			]
 | 
						|
		],
 | 
						|
		'social_a2_stream_action' => [
 | 
						|
			['id'],
 | 
						|
			'social_3_stream_act',
 | 
						|
			[
 | 
						|
				'actor_id_prim' => 'PRIM:actor_id',
 | 
						|
				'stream_id_prim' => 'PRIM:stream_id',
 | 
						|
				'_function_' => 'migrateTableStreamAction'
 | 
						|
			]
 | 
						|
		],
 | 
						|
		'social_a2_stream_queue' => [['id'], 'social_3_stream_queue', []]
 | 
						|
	];
 | 
						|
 | 
						|
	public function __construct(
 | 
						|
		IDBConnection $dbConnection, CoreRequestBuilder $coreRequestBuilder, CheckService $checkService,
 | 
						|
		ConfigService $configService, MiscService $miscService
 | 
						|
	) {
 | 
						|
		parent::__construct();
 | 
						|
		$this->dbConnection = $dbConnection;
 | 
						|
		$this->checkService = $checkService;
 | 
						|
		$this->coreRequestBuilder = $coreRequestBuilder;
 | 
						|
		$this->configService = $configService;
 | 
						|
		$this->miscService = $miscService;
 | 
						|
	}
 | 
						|
 | 
						|
	protected function configure() {
 | 
						|
		parent::configure();
 | 
						|
		$this->setName('social:migrate:alpha3')
 | 
						|
			 ->setDescription('Trying to migrate old data to Alpha3')
 | 
						|
			 ->addOption(
 | 
						|
			 	'remove-migrated-tables', '', InputOption::VALUE_NONE, 'Remove old table once copy is done'
 | 
						|
			 )
 | 
						|
			 ->addOption(
 | 
						|
			 	'force-remove-old-tables', '', InputOption::VALUE_NONE, 'Force remove old tables'
 | 
						|
			 );
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @throws Exception
 | 
						|
	 */
 | 
						|
	protected function execute(InputInterface $input, OutputInterface $output): int {
 | 
						|
		$tables = $this->checkTables();
 | 
						|
 | 
						|
		if ($input->getOption('force-remove-old-tables')) {
 | 
						|
			foreach ($tables as $table) {
 | 
						|
				$this->dropTable($table);
 | 
						|
			}
 | 
						|
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		if (empty($tables)) {
 | 
						|
			$output->writeln('Nothing to migrate.');
 | 
						|
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		$defTables = '';
 | 
						|
		if (sizeof($tables) < sizeof($this->tables)) {
 | 
						|
			$defTables = ': \'' . implode("', '", $tables) . '\'';
 | 
						|
		}
 | 
						|
 | 
						|
		$output->writeln(
 | 
						|
			'Found ' . sizeof($tables) . ' tables to migrate' . $defTables . '.'
 | 
						|
		);
 | 
						|
 | 
						|
		if (!$this->confirmExecute($input, $output)) {
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		$this->done = [];
 | 
						|
		$this->migrateTables($output, $tables);
 | 
						|
 | 
						|
		if ($input->getOption('remove-migrated-tables')) {
 | 
						|
			$this->dropDeprecatedTables($input, $output);
 | 
						|
		}
 | 
						|
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return string[]
 | 
						|
	 */
 | 
						|
	private function checkTables(): array {
 | 
						|
		$ak = array_keys($this->tables);
 | 
						|
		$tables = [];
 | 
						|
		foreach ($ak as $k) {
 | 
						|
			if ($this->dbConnection->tableExists($k)) {
 | 
						|
				$tables[] = $k;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return $tables;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @param InputInterface $input
 | 
						|
	 * @param OutputInterface $output
 | 
						|
	 */
 | 
						|
	private function confirmExecute(InputInterface $input, OutputInterface $output): bool {
 | 
						|
		$helper = $this->getHelper('question');
 | 
						|
		$output->writeln('');
 | 
						|
		$question = new ConfirmationQuestion(
 | 
						|
			'<info>Do you want to migrate data from the old database?</info> (y/N) ', false, '/^(y|Y)/i'
 | 
						|
		);
 | 
						|
 | 
						|
		if (!$helper->ask($input, $output, $question)) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	private function migrateTables(OutputInterface $output, array $tables): void {
 | 
						|
		foreach ($tables as $table) {
 | 
						|
			try {
 | 
						|
				$this->migrateTable($output, $table);
 | 
						|
				$output->writeln('Migration of \'<comment>' . $table . '</comment>\': <info>ok</info>');
 | 
						|
			} catch (Exception $e) {
 | 
						|
				$output->writeln(
 | 
						|
					'Migration of \'<comment>' . $table . '</comment>\': <error>fail</error> - '
 | 
						|
					. $e->getMessage()
 | 
						|
				);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	private function migrateTable(OutputInterface $output, string $table): void {
 | 
						|
		$output->writeln('');
 | 
						|
		$output->writeln('Retrieving data from \'' . $table . '\'.');
 | 
						|
		$fullContent = $this->getContentFromTable($table);
 | 
						|
 | 
						|
		$output->write('Found ' . count($fullContent) . ' entries');
 | 
						|
		$m = $copied = 0;
 | 
						|
		foreach ($fullContent as $entry) {
 | 
						|
			if ($m % 50 === 0) {
 | 
						|
				$output->write('.');
 | 
						|
			}
 | 
						|
 | 
						|
			if ($this->migrateEntry($table, $entry)) {
 | 
						|
				$copied++;
 | 
						|
			}
 | 
						|
 | 
						|
			$m++;
 | 
						|
		}
 | 
						|
 | 
						|
		$output->writeln(' <info>' . $copied . ' copied</info>');
 | 
						|
 | 
						|
		$this->done[] = $table;
 | 
						|
	}
 | 
						|
 | 
						|
	private function getContentFromTable(string $table): array {
 | 
						|
		$qb = $this->dbConnection->getQueryBuilder();
 | 
						|
 | 
						|
		$qb->select('*')
 | 
						|
		   ->from($table);
 | 
						|
 | 
						|
		$entries = [];
 | 
						|
		$cursor = $qb->execute();
 | 
						|
		while ($data = $cursor->fetch()) {
 | 
						|
			$entries[] = $data;
 | 
						|
		}
 | 
						|
		$cursor->closeCursor();
 | 
						|
 | 
						|
		return $entries;
 | 
						|
	}
 | 
						|
 | 
						|
	private function migrateEntry(string $table, $entry): bool {
 | 
						|
		if (!$this->checkUnique($table, $entry)) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		list(, $destTable, $destDefault) = $this->tables[$table];
 | 
						|
 | 
						|
		$qb = $this->dbConnection->getQueryBuilder();
 | 
						|
 | 
						|
		$qb->insert($destTable);
 | 
						|
		$ak = array_merge(array_keys($entry), array_keys($destDefault));
 | 
						|
		foreach ($ak as $k) {
 | 
						|
			if ($k === '_function_') {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			$value = '';
 | 
						|
 | 
						|
			try {
 | 
						|
				if ($this->get($k, $entry, '') !== '') {
 | 
						|
					$this->manageDefault($qb, $this->get($k, $destDefault), $entry);
 | 
						|
					$value = $entry[$k];
 | 
						|
				} elseif (array_key_exists($k, $destDefault)) {
 | 
						|
					$value = $this->manageDefault($qb, $destDefault[$k], $entry);
 | 
						|
				}
 | 
						|
			} catch (Exception $e) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			if ($value !== '') {
 | 
						|
				$qb->setValue($k, $qb->createNamedParameter($value));
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (array_key_exists('_function_', $destDefault)) {
 | 
						|
			call_user_func_array([$this, $destDefault['_function_']], [$qb, $entry]);
 | 
						|
		}
 | 
						|
 | 
						|
		$qb->execute();
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	private function checkUnique(string $table, $entry): bool {
 | 
						|
		list($unique, $destTable) = $this->tables[$table];
 | 
						|
 | 
						|
		$qb = $this->dbConnection->getQueryBuilder();
 | 
						|
		$qb->select('*')
 | 
						|
		   ->from($destTable);
 | 
						|
 | 
						|
		$expr = $qb->expr();
 | 
						|
		$andX = $expr->andX();
 | 
						|
		foreach ($unique as $f) {
 | 
						|
			$andX->add($expr->eq($f, $qb->createNamedParameter($entry[$f])));
 | 
						|
		}
 | 
						|
		$qb->andWhere($andX);
 | 
						|
 | 
						|
		$cursor = $qb->executeQuery();
 | 
						|
		$data = $cursor->fetch();
 | 
						|
		$cursor->closeCursor();
 | 
						|
 | 
						|
		if ($data === false) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @return IParameter|string
 | 
						|
	 * @throws Exception
 | 
						|
	 */
 | 
						|
	private function manageDefault(IQueryBuilder $qb, string $default, array $entry) {
 | 
						|
		if ($default === '') {
 | 
						|
			return '';
 | 
						|
		}
 | 
						|
 | 
						|
		if (!strpos($default, ':')) {
 | 
						|
			return $qb->createNamedParameter($default);
 | 
						|
		}
 | 
						|
 | 
						|
		list($k, $v) = explode(':', $default, 2);
 | 
						|
		switch ($k) {
 | 
						|
			case 'COPY':
 | 
						|
				return $this->get($v, $entry, '');
 | 
						|
 | 
						|
			case 'PRIM':
 | 
						|
				if ($this->get($v, $entry, '') === '') {
 | 
						|
					return '';
 | 
						|
				}
 | 
						|
 | 
						|
				return hash('sha512', $entry[$v]);
 | 
						|
 | 
						|
			case 'REMOVED':
 | 
						|
				throw new Exception();
 | 
						|
		}
 | 
						|
 | 
						|
		return '';
 | 
						|
	}
 | 
						|
 | 
						|
	private function dropDeprecatedTables(InputInterface $input, OutputInterface $output) {
 | 
						|
		$helper = $this->getHelper('question');
 | 
						|
		$output->writeln('');
 | 
						|
		$question = new ConfirmationQuestion(
 | 
						|
			'<info>You migrate ' . count($this->done) . ' table. Do you want to remove them ?</info> (y/N) ',
 | 
						|
			false, '/^(y|Y)/i'
 | 
						|
		);
 | 
						|
 | 
						|
		if (!$helper->ask($input, $output, $question)) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		foreach ($this->done as $table) {
 | 
						|
			$this->dropTable($table);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	private function dropTable(string $table): void {
 | 
						|
		$this->dbConnection->dropTable($table);
 | 
						|
	}
 | 
						|
 | 
						|
	public function migrateTableStreamAction(IQueryBuilder $qb, array $entry): void {
 | 
						|
		$values = json_decode($entry['values'], true);
 | 
						|
		if ($values === null) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		$liked = ($this->getBool('liked', $values)) ? '1' : '0';
 | 
						|
		$boosted = ($this->getBool('boosted', $values)) ? '1' : '0';
 | 
						|
 | 
						|
		$qb->setValue('liked', $qb->createNamedParameter($liked));
 | 
						|
		$qb->setValue('boosted', $qb->createNamedParameter($boosted));
 | 
						|
	}
 | 
						|
}
 |