Skip to content

Commit 0891e1f

Browse files
committed
Update nzb import
1 parent 6a03c6b commit 0891e1f

3 files changed

Lines changed: 269 additions & 16 deletions

File tree

app/Enums/NzbImportStatus.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Enums;
6+
7+
/**
8+
* NZB import result values used while processing imported NZB files.
9+
*/
10+
enum NzbImportStatus: string
11+
{
12+
case Inserted = 'inserted';
13+
14+
case Duplicate = 'duplicate';
15+
16+
case Blacklisted = 'blacklisted';
17+
18+
case NoGroup = 'nogroup';
19+
20+
case Failed = 'failed';
21+
}

app/Services/Nzb/NzbImportService.php

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\Services\Nzb;
66

7+
use App\Enums\NzbImportStatus;
78
use App\Models\Release;
89
use App\Models\Settings;
910
use App\Models\UsenetGroup;
@@ -95,7 +96,7 @@ public function beginImport(mixed $filesToProcess, bool $useNzbName = false, boo
9596
}
9697

9798
$start = now()->toImmutable()->format('Y-m-d H:i:s');
98-
$nzbsImported = $nzbsSkipped = 0;
99+
$nzbsImported = $nzbsSkipped = $nzbsDuplicate = 0;
99100

100101
// Convert all files to string paths and filter to only process NZB files
101102
$nzbFiles = [];
@@ -160,13 +161,13 @@ public function beginImport(mixed $filesToProcess, bool $useNzbName = false, boo
160161
// Try to insert the NZB details into the DB.
161162
$nzbFileName = $useNzbName === true ? str_ireplace('.nzb', '', basename($nzbFilePath)) : '';
162163
try {
163-
$inserted = $this->scanNZBFile($nzbXML, $nzbFileName, $source);
164+
$importStatus = $this->scanNZBFile($nzbXML, $nzbFileName, $source);
164165
} catch (\Exception $e) {
165166
$this->echoOut('ERROR: Problem inserting: '.$nzbFilePath);
166-
$inserted = false;
167+
$importStatus = NzbImportStatus::Failed;
167168
}
168169

169-
if ($inserted) {
170+
if ($importStatus === NzbImportStatus::Inserted) {
170171
// Try to copy the NZB to the NZB folder.
171172
$path = $this->nzb->getNzbPath($this->relGuid, 0, true);
172173

@@ -195,24 +196,41 @@ public function beginImport(mixed $filesToProcess, bool $useNzbName = false, boo
195196
$nzbsImported++;
196197
}
197198
} else {
198-
$this->echoOut('ERROR: Failed to insert NZB!');
199-
if ($deleteFailed) {
200-
File::delete($nzbFilePath);
199+
if ($importStatus === NzbImportStatus::Duplicate) {
200+
$nzbsDuplicate++;
201+
202+
if ($delete || $deleteFailed) {
203+
File::delete($nzbFilePath);
204+
}
205+
} else {
206+
if (in_array($importStatus, [NzbImportStatus::Blacklisted, NzbImportStatus::NoGroup], true)) {
207+
if ($delete || $deleteFailed) {
208+
File::delete($nzbFilePath);
209+
}
210+
} else {
211+
$this->echoOut('ERROR: Failed to insert NZB!');
212+
if ($deleteFailed) {
213+
File::delete($nzbFilePath);
214+
}
215+
}
216+
217+
$nzbsSkipped++;
201218
}
202-
$nzbsSkipped++;
203219
}
204220
} else {
205221
$this->echoOut('ERROR: Unable to fetch: '.$nzbFilePath);
206222
$nzbsSkipped++;
207223
}
208224
}
209225
$this->echoOut(
210-
'Proccessed '.
226+
'Processed '.
211227
$nzbsImported.
212228
' NZBs in '.
213229
now()->diffInSeconds($start, true).' seconds, '.
214230
$nzbsSkipped.
215-
' NZBs were skipped.'
231+
' NZBs were skipped, '.
232+
$nzbsDuplicate.
233+
' were duplicates.'
216234
);
217235

218236
if ($this->browser) {
@@ -227,7 +245,7 @@ public function beginImport(mixed $filesToProcess, bool $useNzbName = false, boo
227245
*
228246
* @throws \Exception
229247
*/
230-
protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $source = ''): bool
248+
protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $source = ''): NzbImportStatus
231249
{
232250
$binary_names = [];
233251
$totalFiles = $totalSize = $groupID = 0;
@@ -314,7 +332,7 @@ protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $s
314332
// Persist blacklist usage stats if we matched any rule during this NZB processing
315333
$this->blacklistService->updateBlacklistUsage($this->blacklistService->getAndClearIdsToUpdate()); // @phpstan-ignore argument.type
316334

317-
return false;
335+
return $isBlackListed ? NzbImportStatus::Blacklisted : NzbImportStatus::NoGroup;
318336
}
319337
}
320338

@@ -352,7 +370,7 @@ protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $s
352370
*
353371
* @throws \Exception
354372
*/
355-
protected function insertNZB(mixed $nzbDetails): bool
373+
protected function insertNZB(mixed $nzbDetails): NzbImportStatus
356374
{
357375
// Make up a GUID for the release.
358376
$this->relGuid = Str::uuid()->toString();
@@ -405,16 +423,16 @@ protected function insertNZB(mixed $nzbDetails): bool
405423
} else {
406424
$this->echoOut('This release is already in our DB so skipping: '.$subject);
407425

408-
return false;
426+
return NzbImportStatus::Duplicate;
409427
}
410428

411429
if ($relID === null) {
412430
$this->echoOut('ERROR: Problem inserting: '.$subject);
413431

414-
return false;
432+
return NzbImportStatus::Failed;
415433
}
416434

417-
return true;
435+
return NzbImportStatus::Inserted;
418436
}
419437

420438
/**
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit;
6+
7+
use App\Enums\NzbImportStatus;
8+
use App\Services\Nzb\NzbImportService;
9+
use Illuminate\Contracts\Console\Kernel;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Facades\DB;
12+
use PDO;
13+
use Tests\TestCase;
14+
15+
final class NzbImportServiceTest extends TestCase
16+
{
17+
private string $databasePath;
18+
19+
/**
20+
* @var array<string, string|false>
21+
*/
22+
private array $originalEnvironment = [];
23+
24+
public function createApplication()
25+
{
26+
$this->databasePath = sys_get_temp_dir().'/nntmux-nzb-import-test.sqlite';
27+
28+
$this->originalEnvironment = [
29+
'APP_ENV' => getenv('APP_ENV'),
30+
'DB_CONNECTION' => getenv('DB_CONNECTION'),
31+
'DB_DATABASE' => getenv('DB_DATABASE'),
32+
];
33+
34+
if (file_exists($this->databasePath)) {
35+
unlink($this->databasePath);
36+
}
37+
38+
$pdo = new PDO('sqlite:'.$this->databasePath);
39+
$pdo->exec('CREATE TABLE settings (name VARCHAR PRIMARY KEY, value TEXT NULL)');
40+
$pdo->exec("INSERT INTO settings (name, value) VALUES
41+
('categorizeforeign', '0'),
42+
('catwebdl', '0'),
43+
('title', 'NNTmux Test'),
44+
('home_link', '/')");
45+
46+
$this->setEnvironmentValue('APP_ENV', 'testing');
47+
$this->setEnvironmentValue('DB_CONNECTION', 'sqlite');
48+
$this->setEnvironmentValue('DB_DATABASE', $this->databasePath);
49+
50+
$app = require __DIR__.'/../../bootstrap/app.php';
51+
52+
$app->make(Kernel::class)->bootstrap();
53+
54+
return $app;
55+
}
56+
57+
protected function setUp(): void
58+
{
59+
parent::setUp();
60+
61+
config([
62+
'database.default' => 'sqlite',
63+
'database.connections.sqlite.database' => $this->databasePath,
64+
'app.key' => 'base64:'.base64_encode(random_bytes(32)),
65+
]);
66+
67+
DB::purge();
68+
DB::reconnect();
69+
Cache::flush();
70+
}
71+
72+
protected function tearDown(): void
73+
{
74+
if ($this->databasePath !== '' && file_exists($this->databasePath)) {
75+
unlink($this->databasePath);
76+
}
77+
78+
parent::tearDown();
79+
80+
foreach ($this->originalEnvironment as $key => $value) {
81+
$this->setEnvironmentValue($key, $value === false ? null : $value);
82+
}
83+
}
84+
85+
public function test_begin_import_uses_specific_messages_and_counts_duplicates_separately(): void
86+
{
87+
$duplicateFile = $this->makeNzbFile('duplicate');
88+
$blacklistedFile = $this->makeNzbFile('blacklisted');
89+
$noGroupFile = $this->makeNzbFile('nogroup');
90+
$failedFile = $this->makeNzbFile('failed');
91+
92+
$service = new class(['Browser' => true], [NzbImportStatus::Duplicate, NzbImportStatus::Blacklisted, NzbImportStatus::NoGroup, NzbImportStatus::Failed]) extends NzbImportService
93+
{
94+
/**
95+
* @param array<NzbImportStatus> $statuses
96+
*/
97+
public function __construct(array $options, private array $statuses)
98+
{
99+
parent::__construct($options);
100+
}
101+
102+
protected function getAllGroups(): bool
103+
{
104+
return true;
105+
}
106+
107+
protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $source = ''): NzbImportStatus
108+
{
109+
$status = array_shift($this->statuses) ?? NzbImportStatus::Failed;
110+
111+
match ($status) {
112+
NzbImportStatus::Duplicate => $this->echoOut('This release is already in our DB so skipping: duplicate subject'),
113+
NzbImportStatus::Blacklisted => $this->echoOut('Subject is blacklisted: blacklisted subject'),
114+
NzbImportStatus::NoGroup => $this->echoOut('No group found for missing-group subject (one of alt.test are missing'),
115+
default => null,
116+
};
117+
118+
return $status;
119+
}
120+
};
121+
122+
$result = $service->beginImport(
123+
[$duplicateFile, $blacklistedFile, $noGroupFile, $failedFile],
124+
delete: false,
125+
deleteFailed: true,
126+
);
127+
128+
$this->assertIsString($result);
129+
$this->assertStringContainsString('This release is already in our DB so skipping: duplicate subject', $result);
130+
$this->assertStringContainsString('Subject is blacklisted: blacklisted subject', $result);
131+
$this->assertStringContainsString('No group found for missing-group subject (one of alt.test are missing', $result);
132+
$this->assertSame(1, substr_count($result, 'ERROR: Failed to insert NZB!'));
133+
$this->assertStringContainsString('Processed 0 NZBs in ', $result);
134+
$this->assertStringContainsString('3 NZBs were skipped, 1 were duplicates.', $result);
135+
136+
$this->assertFileDoesNotExist($duplicateFile);
137+
$this->assertFileDoesNotExist($blacklistedFile);
138+
$this->assertFileDoesNotExist($noGroupFile);
139+
$this->assertFileDoesNotExist($failedFile);
140+
}
141+
142+
public function test_begin_import_deletes_duplicate_blacklisted_and_no_group_files_when_delete_is_enabled(): void
143+
{
144+
$duplicateFile = $this->makeNzbFile('duplicate-delete');
145+
$blacklistedFile = $this->makeNzbFile('blacklisted-delete');
146+
$noGroupFile = $this->makeNzbFile('nogroup-delete');
147+
148+
$service = new class(['Browser' => true], [NzbImportStatus::Duplicate, NzbImportStatus::Blacklisted, NzbImportStatus::NoGroup]) extends NzbImportService
149+
{
150+
/**
151+
* @param array<NzbImportStatus> $statuses
152+
*/
153+
public function __construct(array $options, private array $statuses)
154+
{
155+
parent::__construct($options);
156+
}
157+
158+
protected function getAllGroups(): bool
159+
{
160+
return true;
161+
}
162+
163+
protected function scanNZBFile(mixed &$nzbXML, mixed $nzbFileName = '', mixed $source = ''): NzbImportStatus
164+
{
165+
$status = array_shift($this->statuses) ?? NzbImportStatus::Failed;
166+
167+
match ($status) {
168+
NzbImportStatus::Duplicate => $this->echoOut('This release is already in our DB so skipping: duplicate subject'),
169+
NzbImportStatus::Blacklisted => $this->echoOut('Subject is blacklisted: blacklisted subject'),
170+
NzbImportStatus::NoGroup => $this->echoOut('No group found for missing-group subject (one of alt.test are missing'),
171+
default => null,
172+
};
173+
174+
return $status;
175+
}
176+
};
177+
178+
$result = $service->beginImport(
179+
[$duplicateFile, $blacklistedFile, $noGroupFile],
180+
delete: true,
181+
deleteFailed: false,
182+
);
183+
184+
$this->assertIsString($result);
185+
$this->assertStringNotContainsString('ERROR: Failed to insert NZB!', $result);
186+
$this->assertStringContainsString('2 NZBs were skipped, 1 were duplicates.', $result);
187+
188+
$this->assertFileDoesNotExist($duplicateFile);
189+
$this->assertFileDoesNotExist($blacklistedFile);
190+
$this->assertFileDoesNotExist($noGroupFile);
191+
}
192+
193+
private function makeNzbFile(string $suffix): string
194+
{
195+
$path = sys_get_temp_dir().'/'.$suffix.'-'.bin2hex(random_bytes(5)).'.nzb';
196+
file_put_contents($path, '<nzb></nzb>');
197+
198+
return $path;
199+
}
200+
201+
private function setEnvironmentValue(string $key, ?string $value): void
202+
{
203+
if ($value === null) {
204+
putenv($key);
205+
unset($_ENV[$key], $_SERVER[$key]);
206+
207+
return;
208+
}
209+
210+
putenv("{$key}={$value}");
211+
$_ENV[$key] = $value;
212+
$_SERVER[$key] = $value;
213+
}
214+
}

0 commit comments

Comments
 (0)