Skip to content

Commit

Permalink
Merge pull request #584 from arabcoders/dev
Browse files Browse the repository at this point in the history
Support per user mapper and database.
  • Loading branch information
arabcoders authored Jan 21, 2025
2 parents 254c4e9 + 624ebb1 commit c50c9be
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 84 deletions.
78 changes: 51 additions & 27 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,56 +192,80 @@ jobs:
create_release:
needs: publish_docker_images
runs-on: ubuntu-latest
# Example condition: run on default branch or if triggered manually with create_release == 'true'
# Example condition: run only if this is the default branch, or
# if triggered manually with input "create_release".
if: (endsWith(github.ref, github.event.repository.default_branch) && success()) || (github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'true')

steps:
- name: Check out code
id: checkout_code
uses: actions/checkout@v4
with:
fetch-depth: 0 # so we can do full 'git log' and see all tags
fetch-depth: 0 # so we can see all tags + full history

- name: Determine current branch
id: branch
run: |
# github.ref_name should be "master", "main", or your branch name
echo "BRANCH_NAME=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
- name: Fetch the most recent tag
id: last_tag
- name: Fetch the two latest tags for this branch
id: last_two_tags
run: |
# Make sure we have all tags from the remote
git fetch --tags
# Get the latest tag by commit date (the "newest" tag you pushed).
# If the repo has no tags, fall back to "no-tags-found"
LATEST_TAG=$(git describe --tags --abbrev=0 "$(git rev-list --tags --max-count=1)" 2>/dev/null || echo "no-tags-found")
BRANCH_NAME="${{ steps.branch.outputs.BRANCH_NAME }}"
echo "Current branch: $BRANCH_NAME"
# List tags matching "branchname-*" and sort by *creation date* descending
# Then pick the top 2
LATEST_TAGS=$(git tag --list "${BRANCH_NAME}-*" --sort=-creatordate | head -n 2)
TAG_COUNT=$(echo "$LATEST_TAGS" | wc -l)
echo "Found tags:"
echo "$LATEST_TAGS"
if [ "$TAG_COUNT" -lt 2 ]; then
echo "Not enough tags found (need at least 2) to compare commits."
echo "NOT_ENOUGH_TAGS=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Latest tag found: $LATEST_TAG"
echo "LAST_TAG=$LATEST_TAG" >> "$GITHUB_OUTPUT"
# The first line is the newest tag
TAG_NEWEST=$(echo "$LATEST_TAGS" | sed -n '1p')
# The second line is the previous newest
TAG_PREVIOUS=$(echo "$LATEST_TAGS" | sed -n '2p')
- name: Get commits since last tag
echo "Newest tag: $TAG_NEWEST"
echo "Previous tag: $TAG_PREVIOUS"
# Expose them as outputs for next step
echo "NOT_ENOUGH_TAGS=false" >> "$GITHUB_OUTPUT"
echo "TAG_NEWEST=$TAG_NEWEST" >> "$GITHUB_OUTPUT"
echo "TAG_PREVIOUS=$TAG_PREVIOUS" >> "$GITHUB_OUTPUT"
- name: Generate commit log for newest tag
id: commits
if: steps.last_two_tags.outputs.NOT_ENOUGH_TAGS != 'true'
run: |
# We'll compare the newest tag to HEAD to get a list of commits
LAST_TAG="${{ steps.last_tag.outputs.LAST_TAG }}"
if [ "$LAST_TAG" = "no-tags-found" ]; then
echo "No previous tag found. Listing all commits from the beginning."
LOG=$(git log --pretty=format:"- %h %s by %an")
else
echo "Comparing commits from $LAST_TAG to HEAD"
LOG=$(git log "$LAST_TAG"..HEAD --pretty=format:"- %h %s by %an")
fi
TAG_NEWEST="${{ steps.last_two_tags.outputs.TAG_NEWEST }}"
TAG_PREVIOUS="${{ steps.last_two_tags.outputs.TAG_PREVIOUS }}"
echo "Comparing commits between: $TAG_PREVIOUS..$TAG_NEWEST"
LOG=$(git log "$TAG_PREVIOUS".."$TAG_NEWEST" --pretty=format:"- %h %s by %an")
echo "LOG<<EOF" >> "$GITHUB_ENV"
echo "$LOG" >> "$GITHUB_ENV"
echo "EOF" >> "$GITHUB_ENV"
- name: Create GitHub Release using last tag
- name: Create / Update GitHub Release for the newest tag
if: steps.last_two_tags.outputs.NOT_ENOUGH_TAGS != 'true'
uses: softprops/action-gh-release@master
with:
tag_name: ${{ steps.last_tag.outputs.LAST_TAG }}
name: "${{ steps.last_tag.outputs.LAST_TAG }}"
tag_name: ${{ steps.last_two_tags.outputs.TAG_NEWEST }}
name: "${{ steps.last_two_tags.outputs.TAG_NEWEST }}"
body: ${{ env.LOG }}
append_body: true
generate_release_notes: true
make_latest: true
draft: false
prerelease: false
make_latest: true
token: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions config/directories.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'{path}/db/archive',
'{path}/config',
'{path}/backup',
'{path}/users',
'{tmp_dir}/logs',
'{tmp_dir}/cache',
'{tmp_dir}/profiler',
Expand Down
17 changes: 17 additions & 0 deletions src/Backends/Common/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ public function __construct(private iLogger $logger, private iCache $cache)
{
}

/**
* Clone the object with the given logger and cache adapter.
*
* @param iLogger|null $logger The logger to use. If not provided, the current logger is used.
* @param iCache|null $adapter The cache adapter to use. If not provided, the current cache adapter is used.
*
* @return Cache return new instance of Cache class.
*/
public function with(iLogger|null $logger = null, iCache|null $adapter = null): self
{
$cloned = clone $this;
$cloned->logger = $logger ?? $this->logger;
$cloned->cache = $adapter ?? $this->cache;

return $cloned;
}

/**
* Clone the object with the data retrieved from the cache based on the key.
*
Expand Down
43 changes: 29 additions & 14 deletions src/Commands/State/SyncCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

namespace App\Commands\State;

use App\Backends\Common\Cache as BackendCache;
use App\Backends\Common\ClientInterface as iClient;
use App\Command;
use App\Libs\Attributes\Route\Cli;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\Container;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Extends\StreamLogHandler;
use App\Libs\LogSuppressor;
use App\Libs\Mappers\Import\NullMapper;
use App\Libs\Mappers\ExtendedImportInterface as iEImport;
use App\Libs\Mappers\Import\MemoryMapper;
use App\Libs\Message;
use App\Libs\Options;
use App\Libs\QueueRequests;
Expand Down Expand Up @@ -45,12 +48,12 @@ class SyncCommand extends Command
/**
* Class Constructor.
*
* @param NullMapper $mapper The instance of the DirectMapper class.
* @param MemoryMapper $mapper The instance of the DirectMapper class.
* @param QueueRequests $queue The instance of the QueueRequests class.
* @param iLogger $logger The instance of the iLogger class.
*/
public function __construct(
private readonly NullMapper $mapper,
private readonly MemoryMapper $mapper,
private readonly QueueRequests $queue,
private readonly iLogger $logger,
private readonly LogSuppressor $suppressor,
Expand Down Expand Up @@ -97,12 +100,14 @@ protected function configure(): void
We need the admin token for plex to generate user tokens for each user, and we need the API keys
for jellyfin/emby to get the user list and update their play state.
<question>Known limitions</question>
<question>Known limitations</question>
We have some known limitations,
* Cannot be used with plex users that have PIN enabled.
* Can Only sync played status.
* Cannot sync play progress.
We have some known limitations:
* Cannot be used with plex users that have PIN enabled.
* Can Only sync played status.
* Cannot sync play progress.
Some or all of these limitations will be fixed in future releases.
<question># How does this sync operation mode work?</question>
Expand Down Expand Up @@ -321,8 +326,13 @@ protected function process(iInput $input, iOutput $output): int
]);

foreach (array_reverse($users) as $user) {
$userName = ag($user, 'name', 'Unknown');
$perUserCache = perUserCacheAdapter($userName);
$perUserMapper = perUserMapper($this->mapper, $userName)
->withCache($perUserCache)
->withLogger($this->logger)->loadData();

$this->queue->reset();
$this->mapper->reset();

$list = [];
$displayName = null;
Expand All @@ -331,7 +341,9 @@ protected function process(iInput $input, iOutput $output): int
$name = ag($backend, 'client_data.backendName');
$clientData = ag($backend, 'client_data');
$clientData['name'] = $name;
$clientData['class'] = makeBackend($clientData, $name)->setLogger($this->logger);
$clientData['class'] = makeBackend($clientData, $name, [
BackendCache::class => Container::get(BackendCache::class)->with(adapter: $perUserCache)
])->setLogger($this->logger);
$list[$name] = $clientData;
$displayName = ag($backend, 'client_data.displayName', '??');
}
Expand All @@ -343,9 +355,12 @@ protected function process(iInput $input, iOutput $output): int
'started' => $start,
]);

$this->handleImport($displayName, $list);
assert($perUserMapper instanceof iEImport);
$this->handleImport($perUserMapper, $displayName, $list);

$changes = $this->mapper->computeChanges(array_keys($list));
assert($perUserMapper instanceof MemoryMapper);
/** @var MemoryMapper $changes */
$changes = $perUserMapper->computeChanges(array_keys($list));

foreach ($changes as $b => $changed) {
$count = count($changed);
Expand Down Expand Up @@ -392,15 +407,15 @@ protected function process(iInput $input, iOutput $output): int
return self::SUCCESS;
}

protected function handleImport(string $name, array $backends): void
protected function handleImport(iEImport $mapper, string $name, array $backends): void
{
/** @var array<array-key,ResponseInterface> $queue */
$queue = [];

foreach ($backends as $backend) {
/** @var iClient $client */
$client = ag($backend, 'class');
array_push($queue, ...$client->pull($this->mapper));
array_push($queue, ...$client->pull($mapper));
}

$start = makeDate();
Expand Down
15 changes: 14 additions & 1 deletion src/Libs/Database/DBLayer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class DBLayer implements LoggerAwareInterface

private int $count = 0;

private string $driver;
private string $driver = '';

private array $last = [
'sql' => '',
Expand Down Expand Up @@ -66,6 +66,19 @@ public function __construct(private readonly PDO $pdo, private array $options =
$this->retry = ag($this->options, 'retry', self::LOCK_RETRY);
}

/**
* Create a new instance with the given PDO object and options.
*
* @param PDO $pdo The PDO object.
* @param array|null $options The options to be passed to the new instance, or null to use the current options.
*
* @return self The new instance.
*/
public function withPDO(PDO $pdo, array|null $options = null): self
{
return new self($pdo, $options ?? $this->options);
}

/**
* Execute a SQL statement and return the number of affected rows.
* The execution will be wrapped into {@link DBLayer::wrap()} method. to handle database locks.
Expand Down
29 changes: 24 additions & 5 deletions src/Libs/Database/DatabaseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,33 @@
use Closure;
use DateTimeInterface;
use PDOException;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerInterface as iLogger;

interface DatabaseInterface
{
public const string MIGRATE_UP = 'up';

public const string MIGRATE_DOWN = 'down';

/**
* Create new instance.
* @param iLogger|null $logger Logger to use, if null use default.
* @param DBLayer|null $db Database layer to use, if null use default.
* @param array|null $options PDO options.
*
* @return self Return new instance.
*/
public function with(iLogger|null $logger = null, DBLayer|null $db = null, array|null $options = null): self;

/**
* Set options
*
* @param array $options PDO options
*
* @return self return new instance with options.
*/
public function withOptions(array $options): self;

/**
* Set options
*
Expand Down Expand Up @@ -124,11 +143,11 @@ public function ensureIndex(array $opts = []): mixed;
* Migrate data from old database schema to new one.
*
* @param string $version Version to migrate to.
* @param LoggerInterface|null $logger Logger to use.
* @param iLogger|null $logger Logger to use.
*
* @return mixed Return value depends on the driver.
*/
public function migrateData(string $version, LoggerInterface|null $logger = null): mixed;
public function migrateData(string $version, iLogger|null $logger = null): mixed;

/**
* Is the database up to date with migrations?
Expand Down Expand Up @@ -166,11 +185,11 @@ public function reset(): bool;
/**
* Inject Logger.
*
* @param LoggerInterface $logger
* @param iLogger $logger
*
* @return $this
*/
public function setLogger(LoggerInterface $logger): self;
public function setLogger(iLogger $logger): self;

/**
* Get DBLayer instance.
Expand Down
20 changes: 14 additions & 6 deletions src/Libs/Database/PDO/PDOAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ final class PDOAdapter implements iDB
*/
private bool $viaTransaction = false;

/**
* @var array Adapter options.
*/
private array $options = [];

/**
* @var array<array-key, PDOStatement> Prepared statements.
*/
Expand All @@ -49,8 +44,21 @@ final class PDOAdapter implements iDB
* @param iLogger $logger The logger object used for logging.
* @param DBLayer $db The PDO object used for database connections.
*/
public function __construct(private iLogger $logger, private readonly DBLayer $db)
public function __construct(private iLogger $logger, private readonly DBLayer $db, private array $options = [])
{
}

public function with(iLogger|null $logger = null, DBLayer|null $db = null, array|null $options = null): self
{
if (null === $logger && null === $db && null === $options) {
return $this;
}
return new self($logger ?? $this->logger, $db ?? $this->db, $options ?? $this->options);
}

public function withOptions(array $options): self
{
return $this->with(options: $options);
}

/**
Expand Down
Loading

0 comments on commit c50c9be

Please sign in to comment.