add new timestamp database field. add migration step to convert timestamps to unix epoch

This commit is contained in:
thrillfall 2021-10-05 12:30:52 +02:00
parent 2e8ba1b0ad
commit aa024e55f8
12 changed files with 207 additions and 15 deletions

View File

@ -15,4 +15,9 @@
<php min-version="7.4"/> <php min-version="7.4"/>
<nextcloud min-version="20" max-version="22"/> <nextcloud min-version="20" max-version="22"/>
</dependencies> </dependencies>
<repair-steps>
<post-migration>
<step>OCA\GPodderSync\Migration\TimestampMigration</step>
</post-migration>
</repair-steps>
</info> </info>

View File

@ -55,7 +55,7 @@ class EpisodeActionSaver
return $episodeActionEntities; return $episodeActionEntities;
} }
private function convertTimestampToDbDateTimeString(string $timestamp): string private function convertTimestampTo(string $timestamp): string
{ {
return \DateTime::createFromFormat('D F d H:i:s T Y', $timestamp) return \DateTime::createFromFormat('D F d H:i:s T Y', $timestamp)
->setTimezone(new DateTimeZone('UTC')) ->setTimezone(new DateTimeZone('UTC'))
@ -110,7 +110,7 @@ class EpisodeActionSaver
$episodeActionEntity->setPosition($episodeAction->getPosition()); $episodeActionEntity->setPosition($episodeAction->getPosition());
$episodeActionEntity->setStarted($episodeAction->getStarted()); $episodeActionEntity->setStarted($episodeAction->getStarted());
$episodeActionEntity->setTotal($episodeAction->getTotal()); $episodeActionEntity->setTotal($episodeAction->getTotal());
$episodeActionEntity->setTimestamp($this->convertTimestampToDbDateTimeString($episodeAction->getTimestamp())); $episodeActionEntity->setTimestamp($this->convertTimestampTo($episodeAction->getTimestamp()));
$episodeActionEntity->setUserId($userId); $episodeActionEntity->setUserId($userId);
return $episodeActionEntity; return $episodeActionEntity;

View File

@ -15,6 +15,7 @@ class EpisodeActionEntity extends Entity implements JsonSerializable {
protected $started; protected $started;
protected $total; protected $total;
protected $timestamp; protected $timestamp;
protected $timestampEpoch;
protected $guid; protected $guid;
protected $userId; protected $userId;
@ -33,6 +34,7 @@ class EpisodeActionEntity extends Entity implements JsonSerializable {
'started' => $this->started, 'started' => $this->started,
'total' => $this->total, 'total' => $this->total,
'timestamp' => (new \DateTime($this->timestamp))->format("Y-m-d\TH:i:s"), 'timestamp' => (new \DateTime($this->timestamp))->format("Y-m-d\TH:i:s"),
'timestamp_epoch' => $this->timestampEpoch,
]; ];
} }
} }

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace OCA\GPodderSync\Migration;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use Safe\DateTime;
class TimestampMigration implements \OCP\Migration\IRepairStep
{
private IDBConnection $db;
private IConfig $config;
public function __construct(IDBConnection $db, IConfig $config)
{
$this->db = $db;
$this->config = $config;
}
/**
* @inheritDoc
*/
public function getName() : string
{
return "migrate timestamp values to integer to store unix epoch";
}
/**
* @inheritDoc
*/
public function run(IOutput $output)
{
$installedVersion = $this->config->getAppValue('gpoddersync', 'installed_version');
if (\version_compare($installedVersion, '2.1.0', '>')) {
return;
}
$queryTimestamps = 'SELECT id, timestamp FROM `*PREFIX*gpodder_episode_action` WHERE timestamp_epoch = 0';
$timestamps = $this->db->executeQuery($queryTimestamps)->fetchAll();
foreach ($timestamps as $timestamp) {
$timestampEpoch = (new DateTime($timestamp["timestamp"]))->format("U");
$sql = 'UPDATE `*PREFIX*gpodder_episode_action` '
. 'SET `timestamp_epoch` = ' . $timestampEpoch
. 'WHERE `timestamp_epoch` = 0';
$result = $this->db->executeUpdate($sql);
}
return $result;
}
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace OCA\GPodderSync\Migration; namespace OCA\GPodderSync\Migration;
use Closure; use Closure;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use OCP\DB\ISchemaWrapper; use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput; use OCP\Migration\IOutput;

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace OCA\GPodderSync\Migration;
use Closure;
use Doctrine\DBAL\Types\Types;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version0005Date20211004110900 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('gpodder_episode_action');
$table->addColumn('timestamp_epoch', Types::INTEGER, [
'notnull' => false,
'default' => 0,
'unsigned' => true,
]);
return $schema;
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace tests\Helper\Writer;
class TestWriter implements \OCP\Log\IWriter
{
/**
* @inheritDoc
*/
public function write(string $app, $message, int $level)
{
}
}

View File

@ -3,14 +3,11 @@ declare(strict_types=1);
namespace tests\Integration; namespace tests\Integration;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\DB\Exceptions\DbalException;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionReader;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter; use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\IAppContainer;
use Test\TestCase; use Test\TestCase;
use tests\Helper\DatabaseTransaction; use tests\Helper\DatabaseTransaction;
@ -23,7 +20,7 @@ class EpisodeActionGuidMigrationTest extends TestCase
private const USER_ID_0 = "user0@127.0.0.1"; private const USER_ID_0 = "user0@127.0.0.1";
private \OCP\AppFramework\IAppContainer $container; private IAppContainer $container;
/** /**
* @var EpisodeActionWriter * @var EpisodeActionWriter
@ -71,10 +68,6 @@ class EpisodeActionGuidMigrationTest extends TestCase
} }
/**
*
* @group findme
*/
public function testFindEpisodeActionByEpisodeUrlAndThenGuid() public function testFindEpisodeActionByEpisodeUrlAndThenGuid()
{ {
$episodeActionEntity = new EpisodeActionEntity(); $episodeActionEntity = new EpisodeActionEntity();

View File

@ -44,4 +44,5 @@ class EpisodeActionSaverGuidBackwardCompatbilityTest extends TestCase
self::assertSame($savedEpisodeActionEntity->getId(), $savedEpisodeActionEntityWithoutGuidFromOldDevice->getId()); self::assertSame($savedEpisodeActionEntity->getId(), $savedEpisodeActionEntityWithoutGuidFromOldDevice->getId());
self::assertNotNull($savedEpisodeActionEntityWithoutGuidFromOldDevice->getGuid()); self::assertNotNull($savedEpisodeActionEntityWithoutGuidFromOldDevice->getGuid());
} }
} }

View File

@ -5,6 +5,7 @@ namespace tests\Integration;
use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver; use OCA\GPodderSync\Core\EpisodeAction\EpisodeActionSaver;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\IAppContainer;
use Test\TestCase; use Test\TestCase;
/** /**
@ -12,10 +13,9 @@ use Test\TestCase;
*/ */
class EpisodeActionSaverGuidMigrationTest extends TestCase class EpisodeActionSaverGuidMigrationTest extends TestCase
{ {
private const USER_ID_0 = "testuser0"; private const USER_ID_0 = "testuser0";
private \OCP\AppFramework\IAppContainer $container; private IAppContainer $container;
public function setUp(): void { public function setUp(): void {
parent::setUp(); parent::setUp();
@ -59,9 +59,10 @@ class EpisodeActionSaverGuidMigrationTest extends TestCase
$savedEpisodeActionEntityWithDifferentEpisodeUrl = $episodeActionSaver->saveEpisodeActions( $savedEpisodeActionEntityWithDifferentEpisodeUrl = $episodeActionSaver->saveEpisodeActions(
"[EpisodeAction{podcast='https://rss.art19.com/dr-death-s3-miracle-man', episode='{$episodeUrl}_different', guid='{$guid}', action=PLAY, timestamp=Mon Aug 23 01:58:56 GMT+02:00 2021, started=47, position=54, total=2252}]", "[EpisodeAction{podcast='https://rss.art19.com/dr-death-s3-miracle-man', episode='{$episodeUrl}_different', guid='{$guid}', action=PLAY, timestamp=Mon Aug 23 01:58:56 GMT+02:00 2021, started=47, position=54, total=2252}]",
self::USER_ID_0 self::USER_ID_0
)[0]; )[0];
self::assertSame($savedEpisodeActionEntity->getId(), $savedEpisodeActionEntityWithDifferentEpisodeUrl->getId()); self::assertSame($savedEpisodeActionEntity->getId(), $savedEpisodeActionEntityWithDifferentEpisodeUrl->getId());
} }
} }

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace tests\Integration\Migration;
use OC\AllConfig;
use OC\Log;
use OC\Migration\SimpleOutput;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionEntity;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionRepository;
use OCA\GPodderSync\Db\EpisodeAction\EpisodeActionWriter;
use OCA\GPodderSync\Migration\TimestampMigration;
use OCP\AppFramework\App;
use OCP\IConfig;
use OCP\IDBConnection;
use test\TestCase;
use tests\Helper\DatabaseTransaction;
use tests\Helper\Writer\TestWriter;
/**
* @group DB
*/
class TimestampMigrationTest extends TestCase
{
use DatabaseTransaction;
const TEST_GUID_1234 = "test_uuid_1234";
const ADMIN = "admin";
private EpisodeActionWriter $episodeActionWriter;
private EpisodeActionRepository $episodeActionRepository;
private IDBConnection $dbConnection;
private IConfig $migrationConfig;
public function setUp(): void {
parent::setUp();
$app = new App('gpoddersync');
$this->container = $app->getContainer();
$this->episodeActionWriter = $this->container->get(EpisodeActionWriter::class);
$this->episodeActionRepository = $this->container->get(EpisodeActionRepository::class);
$this->dbConnection = $this->container->get(IDBConnection::class);
$this->migrationConfig = $this->container->get(AllConfig::class );
}
/**
* @before
*/
public function before()
{
$this->startTransaction();
}
public function testTimestampConversionRepairStep()
{
$episodeActionEntity = new EpisodeActionEntity();
$episodeActionEntity->setPodcast("https://podcast_01.url");
$episodeActionEntity->setEpisode(uniqid("https://episode_01.url"));
$episodeActionEntity->setAction("PLAY");
$episodeActionEntity->setPosition(5);
$episodeActionEntity->setStarted(0);
$episodeActionEntity->setTotal(123);
$episodeActionEntity->setTimestamp("Mon Aug 23 01:58:56 GMT+02:00 2021");
$episodeActionEntity->setUserId(self::ADMIN);
$guid = uniqid("self::TEST_GUID_1234");
$episodeActionEntity->setGuid($guid);
$this->episodeActionWriter->save($episodeActionEntity);
$episodeActionEntityBeforeConversion = $this->episodeActionRepository->findByEpisodeIdentifier($guid, self::ADMIN);
$this->assertEquals(
0,
$episodeActionEntityBeforeConversion->getTimestampEpoch()
);
$timestampMigration = new TimestampMigration($this->dbConnection, $this->migrationConfig);
$timestampMigration->run(new SimpleOutput(new Log(new TestWriter()), "gpoddersync"));
$episodeActionEntityAfterConversion = $this->episodeActionRepository->findByEpisodeIdentifier($guid, self::ADMIN);
$this->assertSame(
(int)(new \DateTime($episodeActionEntity->getTimestamp()))->format("U"),
$episodeActionEntityAfterConversion->getTimestampEpoch()
);
}
/**
* @after
*/
public function after()
{
$this->rollbackTransation();
}
}

View File

@ -6,6 +6,7 @@ if (!defined('PHPUNIT_RUN')) {
require_once __DIR__ . '/../../../lib/base.php'; require_once __DIR__ . '/../../../lib/base.php';
require_once __DIR__ . '/Helper/DatabaseTransaction.php'; require_once __DIR__ . '/Helper/DatabaseTransaction.php';
require_once __DIR__ . '/Helper/Writer/TestWriter.php';
// Fix for "Autoload path not allowed: .../tests/lib/testcase.php" // Fix for "Autoload path not allowed: .../tests/lib/testcase.php"
OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests');