Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 3 additions & 28 deletions config.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Support\GitHub\GitHubReleaseDownloadsCounter;
use App\Support\Pricing\WooCommerceAuthHeadersBuilder;
use App\Support\Pricing\WooCommerceProductCollection;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -51,6 +52,7 @@
$wooCommerceAuthenticatedHeaders = (new WooCommerceAuthHeadersBuilder())
->build($wooCommerceConsumerKey, $wooCommerceConsumerSecret);

$gitHubReleaseDownloadsCounter = new GitHubReleaseDownloadsCounter();
$wooCommerceProductCollection = new WooCommerceProductCollection($wooCommerceAuthenticatedHeaders);

return [
Expand All @@ -63,34 +65,7 @@
'title' => 'LibreSign - Open Source Electronic Signature for Nextcloud',
'description' => 'LibreSign is a free and open source electronic signature app for Nextcloud. Sign, request, and manage digital documents securely in your own self-hosted environment.',
'authorGravatars' => $authorGravatars,
'githubDownloads' => (function() {
$total = 0;
$page = 1;
$token = getenv('GITHUB_TOKEN');
$headers = ['User-Agent: libresign-site-build'];
if ($token) {
$headers[] = 'Authorization: Bearer ' . $token;
}
$context = stream_context_create(['http' => [
'header' => implode("\r\n", $headers),
'timeout' => 15,
]]);
while (true) {
$url = "https://api.github.com/repos/LibreSign/libresign/releases?per_page=100&page={$page}";
$json = @file_get_contents($url, false, $context);
if ($json === false) break;
$releases = json_decode($json, true);
if (empty($releases)) break;
foreach ($releases as $release) {
foreach ($release['assets'] ?? [] as $asset) {
$total += $asset['download_count'] ?? 0;
}
}
if (count($releases) < 100) break;
$page++;
}
return $total > 0 ? $total : null;
})(),
'githubDownloads' => $gitHubReleaseDownloadsCounter->count(getenv('GITHUB_TOKEN') ?: null),
'locales' => function ($page) {
return available_locales($page);
},
Expand Down
67 changes: 67 additions & 0 deletions support/GitHub/GitHubReleaseDownloadsCounter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace App\Support\GitHub;

class GitHubReleaseDownloadsCounter
{
public function __construct(
private readonly string $repository = 'LibreSign/libresign',
private readonly int $timeout = 15,
) {
}

public function count(?string $token = null): ?int
{
$total = 0;
$page = 1;

while (true) {
$releases = $this->fetchPage($page, $token);

if (empty($releases)) {
break;
}

foreach ($releases as $release) {
foreach ($release['assets'] ?? [] as $asset) {
$total += $asset['download_count'] ?? 0;
}
}

if (count($releases) < 100) {
break;
}

$page++;
}

return $total > 0 ? $total : null;
}

protected function fetchPage(int $page, ?string $token): ?array
{
$headers = ['User-Agent: libresign-site-build'];
if (!empty($token)) {
$headers[] = 'Authorization: Bearer ' . $token;
}

$context = stream_context_create([
'http' => [
'header' => implode("\r\n", $headers),
'timeout' => $this->timeout,
],
]);

$url = sprintf(
'https://api.github.com/repos/%s/releases?per_page=100&page=%d',
$this->repository,
$page,
);

$json = @file_get_contents($url, false, $context);

return $json ? (json_decode($json, true) ?: null) : null;
}
}
69 changes: 69 additions & 0 deletions tests/Unit/Support/GitHub/GitHubReleaseDownloadsCounterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace Tests\Unit\Support\GitHub;

use App\Support\GitHub\GitHubReleaseDownloadsCounter;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class GitHubReleaseDownloadsCounterTest extends TestCase
{
#[DataProvider('countProvider')]
public function testCount(array $pages, ?int $expected): void
{
$counter = new FakeGitHubReleaseDownloadsCounter($pages);

self::assertSame($expected, $counter->count('token'));
}

public static function countProvider(): iterable
{
yield 'returns null when first page fails' => [
[],
null,
];

yield 'returns null when releases are empty' => [
[1 => []],
null,
];

yield 'sums downloads from a single page' => [
[
1 => [
['assets' => [['download_count' => 10], ['download_count' => 5]]],
['assets' => [['download_count' => 7]]],
],
],
22,
];

yield 'continues to the second page when first page has 100 items' => [
[
1 => array_fill(0, 100, ['assets' => [['download_count' => 1]]]),
2 => [
['assets' => [['download_count' => 3]]],
],
],
103,
];
}
}

final class FakeGitHubReleaseDownloadsCounter extends GitHubReleaseDownloadsCounter
{
/**
* @param array<int, array<int, array<string, mixed>>> $pages
*/
public function __construct(private readonly array $pages)
{
parent::__construct();
}

protected function fetchPage(int $page, ?string $token): ?array
{
return $this->pages[$page] ?? null;
}
}
Loading