add new timestamp database field. add migration step to convert timestamps to unix epoch
This commit is contained in:
parent
2e8ba1b0ad
commit
aa024e55f8
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
lib/Migration/TimestampMigration.php
Normal file
57
lib/Migration/TimestampMigration.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
26
lib/Migration/Version0005Date20211004110900.php
Normal file
26
lib/Migration/Version0005Date20211004110900.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
15
tests/Helper/Writer/TestWriter.php
Normal file
15
tests/Helper/Writer/TestWriter.php
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
92
tests/Integration/Migration/TimestampMigrationTest.php
Normal file
92
tests/Integration/Migration/TimestampMigrationTest.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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');
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user