Skip to content

Commit

Permalink
Chore: Migrate from old serialized format to new table format (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikudouSage authored Sep 7, 2024
1 parent ffb14b1 commit 4943ea9
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 11 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
> Important: Version 1.6.0 uses a new database format. All newer versions will migrate your existing data
> but it's not possible to go back from 1.6.0 to a previous version.
# gog-downloader

PHP based tool to download the games you have bought on GOG to your hard drive.
Expand Down
59 changes: 59 additions & 0 deletions src/Migration/Migration3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\Migration;

use App\DTO\GameDetail;
use PDO;

final readonly class Migration3 implements Migration
{
public function migrate(PDO $pdo): void
{
$originalRows = $pdo->query('select * from games')->fetchAll(PDO::FETCH_ASSOC);
/** @var array<GameDetail> $games */
$games = array_map(
fn (array $row) => unserialize($row['serializedData']),
$originalRows,
);

$pdo->exec('drop table games');
$pdo->exec('create table games (id integer primary key autoincrement, title string, cd_key string, game_id integer, unique (game_id))');
$pdo->exec('create table downloads (
id integer primary key autoincrement,
language string,
platform string,
name string,
size float,
url string,
md5 string,
game_id integer,
foreign key (game_id) references games(id) on delete cascade on update cascade
)');

foreach ($games as $game) {
$pdo->prepare('insert into games (game_id, title, cd_key) values (?, ?, ?)')->execute([
$game->id,
$game->title,
$game->cdKey ?: null,
]);
$rowId = $pdo->lastInsertId();

foreach ($game->downloads as $download) {
$pdo->prepare('insert into downloads (language, platform, name, size, url, md5, game_id) values (?, ?, ?, ?, ?, ?, ?)')->execute([
$download->language,
$download->platform,
$download->name,
$download->size,
$download->url,
$download->md5,
$rowId,
]);
}
}
}

public function getVersion(): int
{
return 3;
}
}
12 changes: 8 additions & 4 deletions src/Service/Factory/PersistenceManagerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
use App\Service\Persistence\PersistenceManager;
use App\Service\Persistence\PersistenceManagerFiles;
use App\Service\Persistence\PersistenceManagerSqlite;
use App\Service\Serializer;
use PDO;
use SQLite3;

final class PersistenceManagerFactory
final readonly class PersistenceManagerFactory
{
public function __construct(
private readonly MigrationManager $migrationManager,
private MigrationManager $migrationManager,
private Serializer $serializer,
) {
}

public function getPersistenceManager(): PersistenceManager
{
if (class_exists(\PDO::class, false) && class_exists(\SQLite3::class, false)) {
return new PersistenceManagerSqlite($this->migrationManager);
if (class_exists(PDO::class, false) && class_exists(SQLite3::class, false)) {
return new PersistenceManagerSqlite($this->migrationManager, $this->serializer);
}

return new PersistenceManagerFiles();
Expand Down
49 changes: 44 additions & 5 deletions src/Service/Persistence/PersistenceManagerSqlite.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\DTO\GameDetail;
use App\Enum\Setting;
use App\Service\MigrationManager;
use App\Service\Serializer;
use DateTimeImmutable;
use JetBrains\PhpStorm\ExpectedValues;
use PDO;
Expand All @@ -16,6 +17,7 @@ final class PersistenceManagerSqlite extends AbstractPersistenceManager

public function __construct(
private readonly MigrationManager $migrationManager,
private readonly Serializer $serializer,
) {
}

Expand Down Expand Up @@ -90,7 +92,16 @@ public function getLocalGameData(): ?array

$result = [];
while ($next = $query->fetch(PDO::FETCH_ASSOC)) {
$result[] = unserialize($next['serializedData']);
$downloadsQuery = $pdo->prepare('select * from downloads where game_id = ?');
$downloadsQuery->execute([$next['id']]);
$downloads = $downloadsQuery->fetchAll(PDO::FETCH_ASSOC);

$result[] = $this->serializer->deserialize([
'id' => $next['game_id'],
'title' => $next['title'],
'cdKey' => $next['cd_key'],
'downloads' => $downloads,
], GameDetail::class);
}

return $result;
Expand All @@ -101,10 +112,35 @@ public function storeSingleGameDetail(GameDetail $detail): void
$pdo = $this->getPdo(self::DATABASE);
$this->migrationManager->apply($pdo);

$prepared = $pdo->prepare('insert into games (serializedData) VALUES (?)');
$prepared->execute([
serialize($detail),
$pdo->prepare(
'insert into games (title, cd_key, game_id)
VALUES (?, ?, ?)
ON CONFLICT DO UPDATE SET title = excluded.title,
cd_key = excluded.cd_key,
game_id = excluded.game_id
'
)->execute([
$detail->title,
$detail->cdKey ?: null,
$detail->id,
]);

$query = $pdo->prepare('select id from games where game_id = ?');
$query->execute([$detail->id]);
$id = $query->fetch(PDO::FETCH_ASSOC)['id'];

$pdo->prepare('delete from downloads where game_id = ?')->execute([$id]);
foreach ($detail->downloads as $download) {
$pdo->prepare('insert into downloads (language, platform, name, size, url, md5, game_id) VALUES (?, ?, ?, ?, ?, ?, ?)')->execute([
$download->language,
$download->platform,
$download->name,
$download->size,
$download->url,
$download->md5,
$id,
]);
}
}

public function storeSetting(Setting $setting, float|bool|int|string|null $value): void
Expand Down Expand Up @@ -163,6 +199,9 @@ private function getPdo(
#[ExpectedValues(valuesFromClass: self::class)]
string $file
): PDO {
return new PDO("sqlite:{$this->getFullPath($file)}");
$pdo = new PDO("sqlite:{$this->getFullPath($file)}");
$pdo->exec('PRAGMA foreign_keys = ON');

return $pdo;
}
}
16 changes: 14 additions & 2 deletions src/Service/Serializer/DownloadDescriptionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
use ReflectionProperty;
use RuntimeException;

final class DownloadDescriptionNormalizer implements MultipleValuesNormalizer
final class DownloadDescriptionNormalizer implements SerializerNormalizer
{
public function __construct(
) {
}

public function normalize(array $value, array $context = []): MultipleValuesWrapper
public function normalize(array $value, array $context = []): MultipleValuesWrapper|DownloadDescription
{
if (isset($value[0])) {
$results = [];
Expand All @@ -34,6 +34,18 @@ public function normalize(array $value, array $context = []): MultipleValuesWrap
return new MultipleValuesWrapper($results);
}

if (isset($value['game_id'])) {
$object = new DownloadDescription();
$this->set($object, 'language', $value['language']);
$this->set($object, 'platform', $value['platform']);
$this->set($object, 'name', $value['name']);
$this->set($object, 'size', $value['size']);
$this->set($object, 'url', $value['url']);
$this->set($object, 'md5', $value['md5']);

return $object;
}

throw new RuntimeException('Invalid download description');
}

Expand Down

0 comments on commit 4943ea9

Please sign in to comment.