Passed
Push — master ( ae3f18...b8bba4 )
by Darko
10:59
created

HeaderStorageService::getFileCount()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Services\Binaries;
6
7
use Illuminate\Support\Facades\Log;
8
9
/**
10
 * Orchestrates the header storage process.
11
 *
12
 * This service coordinates the CollectionHandler, BinaryHandler, PartHandler,
13
 * and HeaderStorageTransaction to store parsed headers into the database.
14
 */
15
final class HeaderStorageService
16
{
17
    private CollectionHandler $collectionHandler;
18
19
    private BinaryHandler $binaryHandler;
20
21
    private PartHandler $partHandler;
22
23
    private BinariesConfig $config;
0 ignored issues
show
Bug introduced by
The type App\Services\Binaries\BinariesConfig was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
24
25
    /** @var array<int> Article numbers that failed to insert */
26
    private array $failedInserts = [];
27
28
    public function __construct(
29
        ?CollectionHandler $collectionHandler = null,
30
        ?BinaryHandler $binaryHandler = null,
31
        ?PartHandler $partHandler = null,
32
        ?BinariesConfig $config = null
33
    ) {
34
        $this->config = $config ?? BinariesConfig::fromSettings();
35
        $this->collectionHandler = $collectionHandler ?? new CollectionHandler;
36
        $this->binaryHandler = $binaryHandler ?? new BinaryHandler;
37
        $this->partHandler = $partHandler ?? new PartHandler(
38
            $this->config->partsChunkSize,
39
            true
40
        );
41
    }
42
43
    /**
44
     * Store parsed headers to the database.
45
     *
46
     * @param  array  $headers  Parsed headers with 'matches' already populated
47
     * @param  array  $groupMySQL  Group info from database
48
     * @param  bool  $addToPartRepair  Whether to track failed inserts
49
     * @return array Article numbers that failed to insert
50
     */
51
    public function store(array $headers, array $groupMySQL, bool $addToPartRepair = true): array
52
    {
53
        if (empty($headers)) {
54
            return [];
55
        }
56
57
        // Reset all handlers
58
        $this->collectionHandler->reset();
59
        $this->binaryHandler->reset();
60
        $this->partHandler->reset();
61
        $this->partHandler->setAddToPartRepair($addToPartRepair);
62
        $this->failedInserts = [];
63
64
        // Create transaction
65
        $transaction = new HeaderStorageTransaction(
66
            $this->collectionHandler,
67
            $this->binaryHandler,
68
            $this->partHandler
69
        );
70
71
        $transaction->begin();
72
73
        // Process each header
74
        foreach ($headers as $header) {
75
            if (! $this->processHeader($header, $groupMySQL, $transaction)) {
76
                if ($addToPartRepair && isset($header['Number'])) {
77
                    $this->failedInserts[] = $header['Number'];
78
                }
79
            }
80
        }
81
82
        // Flush remaining parts
83
        if ($this->partHandler->hasPending()) {
84
            if (! $this->partHandler->flush()) {
85
                $transaction->markError();
86
            }
87
        }
88
89
        // Flush binary aggregate updates
90
        if (! $transaction->hasErrors()) {
91
            if (! $this->binaryHandler->flushUpdates($this->config->binariesUpdateChunkSize)) {
92
                $transaction->markError();
93
            }
94
        }
95
96
        // Finish transaction
97
        if (! $transaction->finish()) {
98
            // All failed
99
            if ($addToPartRepair) {
100
                return array_unique(array_merge(
101
                    $this->failedInserts,
102
                    $this->partHandler->getFailedNumbers()
103
                ));
104
            }
105
106
            return [];
107
        }
108
109
        return array_unique(array_merge(
110
            $this->failedInserts,
111
            $this->partHandler->getFailedNumbers()
112
        ));
113
    }
114
115
    private function processHeader(array $header, array $groupMySQL, HeaderStorageTransaction $transaction): bool
116
    {
117
        // Get file count from subject
118
        $fileCount = $this->getFileCount($header['matches'][1]);
119
        if ($fileCount[1] === 0 && $fileCount[3] === 0) {
120
            $fileCount = $this->getFileCount($header['matches'][0]);
121
        }
122
123
        $totalFiles = (int) $fileCount[3];
124
        $fileNumber = (int) $fileCount[1];
125
126
        // Get or create collection
127
        $collectionId = $this->collectionHandler->getOrCreateCollection(
128
            $header,
129
            $groupMySQL['id'],
130
            $groupMySQL['name'],
131
            $totalFiles,
132
            $transaction->getBatchNoise()
133
        );
134
135
        if ($collectionId === null) {
136
            $transaction->markError();
137
138
            return false;
139
        }
140
141
        // Get or create binary
142
        $binaryId = $this->binaryHandler->getOrCreateBinary(
143
            $header,
144
            $collectionId,
145
            $groupMySQL['id'],
146
            $fileNumber
147
        );
148
149
        if ($binaryId === null) {
150
            $transaction->markError();
151
152
            return false;
153
        }
154
155
        // Add part
156
        if (! $this->partHandler->addPart($binaryId, $header)) {
157
            $transaction->markError();
158
159
            return false;
160
        }
161
162
        return true;
163
    }
164
165
    private function getFileCount(string $subject): array
166
    {
167
        if (! preg_match('/[[(\s](\d{1,5})(\/|[\s_]of[\s_]|-)(\d{1,5})[])[\s$:]/i', $subject, $fileCount)) {
168
            $fileCount[1] = $fileCount[3] = 0;
169
        }
170
171
        return $fileCount;
172
    }
173
}
174
175