2020-06-23 12:11:49 +00:00
|
|
|
<?php
|
2022-04-15 11:34:01 +00:00
|
|
|
|
2020-06-23 12:11:49 +00:00
|
|
|
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;
|
|
|
|
|
2022-06-17 14:29:54 +00:00
|
|
|
use OCA\Social\Tools\Traits\TArrayTools;
|
2020-06-23 12:11:49 +00:00
|
|
|
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;
|
|
|
|
|
2022-04-15 11:01:18 +00:00
|
|
|
private IDBConnection $dbConnection;
|
|
|
|
private CoreRequestBuilder $coreRequestBuilder;
|
|
|
|
private CheckService $checkService;
|
|
|
|
private ConfigService $configService;
|
|
|
|
private MiscService $miscService;
|
|
|
|
private array $done = [];
|
|
|
|
public array $tables = [
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_actions' => [
|
2020-06-23 12:11:49 +00:00
|
|
|
['id_prim'],
|
|
|
|
'social_3_action',
|
|
|
|
[
|
2022-04-15 11:34:01 +00:00
|
|
|
'actor_id_prim' => 'PRIM:actor_id',
|
2020-06-23 12:11:49 +00:00
|
|
|
'object_id_prim' => 'PRIM:object_id'
|
|
|
|
]
|
|
|
|
],
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_actors' => [['user_id'], 'social_3_actor', []],
|
|
|
|
'social_a2_cache_actors' => [['id_prim'], 'social_3_cache_actor', []],
|
2020-06-23 12:11:49 +00:00
|
|
|
'social_a2_cache_documts' => [['id_prim'], 'social_3_cache_doc', []],
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_follows' => [
|
2020-06-23 12:11:49 +00:00
|
|
|
['id_prim'],
|
|
|
|
'social_3_follow',
|
|
|
|
[
|
2022-04-15 11:34:01 +00:00
|
|
|
'actor_id_prim' => 'PRIM:actor_id',
|
2020-06-23 12:11:49 +00:00
|
|
|
'object_id_prim' => 'PRIM:object_id',
|
|
|
|
'follow_id_prim' => 'PRIM:follow_id'
|
|
|
|
]
|
|
|
|
],
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_hashtags' => [['hashtag'], 'social_3_hashtag', []],
|
2020-06-23 12:11:49 +00:00
|
|
|
'social_a2_request_queue' => [['id'], 'social_3_req_queue', []],
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_stream' => [
|
2020-06-23 12:11:49 +00:00
|
|
|
['id_prim'],
|
|
|
|
'social_3_stream',
|
|
|
|
[
|
2022-04-15 11:34:01 +00:00
|
|
|
'object_id_prim' => 'PRIM:object_id',
|
|
|
|
'in_reply_to_prim' => 'PRIM:in_reply_to',
|
2020-06-23 12:11:49 +00:00
|
|
|
'attributed_to_prim' => 'PRIM:attributed_to',
|
2022-04-15 11:34:01 +00:00
|
|
|
'filter_duplicate' => 'COPY:hidden_on_timeline',
|
2020-06-23 12:11:49 +00:00
|
|
|
'hidden_on_timeline' => 'REMOVED:'
|
|
|
|
]
|
|
|
|
],
|
|
|
|
'social_a2_stream_action' => [
|
|
|
|
['id'],
|
|
|
|
'social_3_stream_act',
|
|
|
|
[
|
2022-04-15 11:34:01 +00:00
|
|
|
'actor_id_prim' => 'PRIM:actor_id',
|
2020-06-23 12:11:49 +00:00
|
|
|
'stream_id_prim' => 'PRIM:stream_id',
|
2022-04-15 11:34:01 +00:00
|
|
|
'_function_' => 'migrateTableStreamAction'
|
2020-06-23 12:11:49 +00:00
|
|
|
]
|
|
|
|
],
|
2022-04-15 11:34:01 +00:00
|
|
|
'social_a2_stream_queue' => [['id'], 'social_3_stream_queue', []]
|
2020-06-23 12:11:49 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
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(
|
2022-11-21 09:22:42 +00:00
|
|
|
'remove-migrated-tables', '', InputOption::VALUE_NONE, 'Remove old table once copy is done'
|
2020-06-23 12:11:49 +00:00
|
|
|
)
|
|
|
|
->addOption(
|
2022-11-21 09:22:42 +00:00
|
|
|
'force-remove-old-tables', '', InputOption::VALUE_NONE, 'Force remove old tables'
|
2020-06-23 12:11:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
2022-11-20 12:31:31 +00:00
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
2020-06-23 12:11:49 +00:00
|
|
|
$tables = $this->checkTables();
|
|
|
|
|
|
|
|
if ($input->getOption('force-remove-old-tables')) {
|
|
|
|
foreach ($tables as $table) {
|
|
|
|
$this->dropTable($table);
|
|
|
|
}
|
|
|
|
|
2022-11-20 12:31:31 +00:00
|
|
|
return 0;
|
2020-06-23 12:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($tables)) {
|
|
|
|
$output->writeln('Nothing to migrate.');
|
|
|
|
|
2022-11-20 12:31:31 +00:00
|
|
|
return 0;
|
2020-06-23 12:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$defTables = '';
|
|
|
|
if (sizeof($tables) < sizeof($this->tables)) {
|
|
|
|
$defTables = ': \'' . implode("', '", $tables) . '\'';
|
|
|
|
}
|
|
|
|
|
|
|
|
$output->writeln(
|
|
|
|
'Found ' . sizeof($tables) . ' tables to migrate' . $defTables . '.'
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!$this->confirmExecute($input, $output)) {
|
2022-11-20 12:31:31 +00:00
|
|
|
return 0;
|
2020-06-23 12:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->done = [];
|
|
|
|
$this->migrateTables($output, $tables);
|
|
|
|
|
|
|
|
if ($input->getOption('remove-migrated-tables')) {
|
|
|
|
$this->dropDeprecatedTables($input, $output);
|
|
|
|
}
|
2022-11-20 12:31:31 +00:00
|
|
|
|
|
|
|
return 0;
|
2020-06-23 12:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
private function migrateTables(OutputInterface $output, array $tables): void {
|
2020-06-23 12:11:49 +00:00
|
|
|
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()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
private function migrateTable(OutputInterface $output, string $table): void {
|
2020-06-23 12:11:49 +00:00
|
|
|
$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) {
|
2020-06-25 14:10:39 +00:00
|
|
|
if ($m % 50 === 0) {
|
2020-06-23 12:11:49 +00:00
|
|
|
$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];
|
2022-04-15 11:34:01 +00:00
|
|
|
} elseif (array_key_exists($k, $destDefault)) {
|
2020-06-23 12:11:49 +00:00
|
|
|
$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);
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
$cursor = $qb->executeQuery();
|
2020-06-23 12:11:49 +00:00
|
|
|
$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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
private function dropTable(string $table): void {
|
2020-06-23 12:11:49 +00:00
|
|
|
$this->dbConnection->dropTable($table);
|
|
|
|
}
|
|
|
|
|
2022-04-15 11:34:01 +00:00
|
|
|
public function migrateTableStreamAction(IQueryBuilder $qb, array $entry): void {
|
2020-06-23 12:11:49 +00:00
|
|
|
$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));
|
|
|
|
}
|
|
|
|
}
|