Skip to content

Commit 2e552d8

Browse files
authored
Merge pull request #715 from CleanTalk/upd-sfw-upd.ag
upd-sfw-upd.ag
2 parents 08b8c00 + e433087 commit 2e552d8

File tree

12 files changed

+1809
-87
lines changed

12 files changed

+1809
-87
lines changed

cleantalk.php

Lines changed: 5 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,92 +1519,13 @@ function apbct_sfw_update__get_multifiles_of_type(array $params)
15191519

15201520
/**
15211521
* Queue stage. Do load multifiles with networks on their urls.
1522-
* @param $urls
1523-
* @return array|array[]|bool|string|string[]
1522+
* @param $all_urls
1523+
* @return array|true
15241524
*/
1525-
function apbct_sfw_update__download_files($urls, $direct_update = false)
1525+
function apbct_sfw_update__download_files($all_urls, $direct_update = false)
15261526
{
1527-
global $apbct;
1528-
1529-
sleep(3);
1530-
1531-
if ( ! is_writable($apbct->fw_stats['updating_folder']) ) {
1532-
return array('error' => 'SFW update folder is not writable.');
1533-
}
1534-
1535-
//Reset keys
1536-
$urls = array_values(array_unique($urls));
1537-
1538-
$results = array();
1539-
$batch_size = 10;
1540-
1541-
/**
1542-
* Reduce batch size of curl multi instanced
1543-
*/
1544-
if (defined('APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE')) {
1545-
if (
1546-
is_int(APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE) &&
1547-
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE > 0 &&
1548-
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE < 10
1549-
) {
1550-
$batch_size = APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE;
1551-
};
1552-
}
1553-
1554-
$total_urls = count($urls);
1555-
$batches = ceil($total_urls / $batch_size);
1556-
1557-
for ($i = 0; $i < $batches; $i++) {
1558-
$batch_urls = array_slice($urls, $i * $batch_size, $batch_size);
1559-
if (!empty($batch_urls)) {
1560-
$http_results = Helper::httpMultiRequest($batch_urls, $apbct->fw_stats['updating_folder']);
1561-
if (is_array($http_results)) {
1562-
$results = array_merge($results, $http_results);
1563-
}
1564-
// to handle case if we request only one url, then Helper::httpMultiRequest returns string 'success' instead of array
1565-
if (count($batch_urls) === 1 && $http_results === 'success') {
1566-
$results = array_merge($results, $batch_urls);
1567-
}
1568-
}
1569-
}
1570-
1571-
$results = TT::toArray($results);
1572-
$count_urls = count($urls);
1573-
$count_results = count($results);
1574-
1575-
if ( empty($results['error']) && ($count_urls === $count_results) ) {
1576-
if ( $direct_update ) {
1577-
return true;
1578-
}
1579-
$download_again = array();
1580-
$results = array_values($results);
1581-
for ( $i = 0; $i < $count_results; $i++ ) {
1582-
if ( $results[$i] === 'error' ) {
1583-
$download_again[] = $urls[$i];
1584-
}
1585-
}
1586-
1587-
if ( count($download_again) !== 0 ) {
1588-
return array(
1589-
'error' => 'Files download not completed.',
1590-
'update_args' => array(
1591-
'args' => $download_again
1592-
)
1593-
);
1594-
}
1595-
1596-
return array(
1597-
'next_stage' => array(
1598-
'name' => 'apbct_sfw_update__create_tables'
1599-
)
1600-
);
1601-
}
1602-
1603-
if ( ! empty($results['error']) ) {
1604-
return $results;
1605-
}
1606-
1607-
return array('error' => 'Files download not completed.');
1527+
$downloader = new \Cleantalk\ApbctWP\Firewall\SFWFilesDownloader();
1528+
return $downloader->downloadFiles($all_urls, $direct_update);
16081529
}
16091530

16101531
/**
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
3+
namespace Cleantalk\ApbctWP\Firewall;
4+
5+
use Cleantalk\ApbctWP\HTTP\HTTPMultiRequestService;
6+
use Cleantalk\Common\TT;
7+
8+
/**
9+
* Class SFWFilesDownloader
10+
*
11+
* Handles downloading of SpamFireWall data files from remote servers.
12+
* Manages batch processing, retry logic, and error handling for file downloads.
13+
*/
14+
class SFWFilesDownloader
15+
{
16+
/**
17+
* HTTP multi-request service instance
18+
*
19+
* @var HTTPMultiRequestService
20+
*/
21+
private $http_multi_request_service;
22+
23+
/**
24+
* @var string
25+
*/
26+
private $deafult_error_prefix;
27+
28+
/**
29+
* @var string
30+
*/
31+
private $deafult_error_content = 'UNKNOWN ERROR';
32+
33+
/**
34+
* SFWFilesDownloader constructor
35+
*
36+
* @param HTTPMultiRequestService|null $service Optional. Custom service instance for dependency injection.
37+
* @throws \InvalidArgumentException If service is not an instance of HTTPMultiRequestService
38+
*/
39+
public function __construct($service = null)
40+
{
41+
$this->deafult_error_prefix = basename(__CLASS__) . ': ';
42+
43+
if ($service !== null && !$service instanceof HTTPMultiRequestService) {
44+
throw new \InvalidArgumentException(
45+
'Service must be an instance of ' . HTTPMultiRequestService::class
46+
);
47+
}
48+
49+
$this->http_multi_request_service = $service ?: new HTTPMultiRequestService();
50+
}
51+
52+
/**
53+
* Downloads SFW data files from provided URLs with batch processing and retry logic
54+
*
55+
* Downloads files in batches to avoid server overload. Automatically retries failed downloads
56+
* with reduced batch size if necessary. Validates write permissions and URL format before processing.
57+
*
58+
* @param array|mixed $all_urls List of URLs to download files from
59+
* @param bool $direct_update Optional. If true, returns boolean result. If false, returns stage info array.
60+
* @param int sleep Pause in seconds before multi contracts run, default is 3
61+
*
62+
* @return true|array True on success (direct update mode), or array with 'next_stage' key,
63+
* or array with 'error' key on failure, or array with 'update_args' for retry.
64+
*/
65+
public function downloadFiles($all_urls, $direct_update = false, $sleep = 3)
66+
{
67+
global $apbct;
68+
69+
// Delay to prevent server overload
70+
sleep($sleep);
71+
72+
// Validate write permissions for update folder
73+
if ( ! is_writable($apbct->fw_stats['updating_folder']) ) {
74+
return $this->responseStopUpdate('SFW UPDATE FOLDER IS NOT WRITABLE.');
75+
}
76+
77+
// Validate URLs parameter type
78+
if ( ! is_array($all_urls) ) {
79+
return $this->responseStopUpdate('URLS LIST SHOULD BE AN ARRAY');
80+
}
81+
82+
// Remove duplicates and reset array keys to sequential integers
83+
$all_urls = array_values(array_unique($all_urls));
84+
85+
// Get current batch size from settings or default
86+
$work_batch_size = SFWUpdateHelper::getSFWFilesBatchSize();
87+
88+
// Initialize batch processing variables
89+
$total_urls = count($all_urls);
90+
$batches = ceil($total_urls / $work_batch_size);
91+
$download_again = [];
92+
$written_urls = [];
93+
94+
// Get or set default batch size for retry attempts
95+
$on_repeat_batch_size = !empty($apbct->data['sfw_update__batch_size'])
96+
? TT::toInt($apbct->data['sfw_update__batch_size'])
97+
: 10;
98+
99+
// Process URLs in batches
100+
for ($i = 0; $i < $batches; $i++) {
101+
// Extract current batch of URLs
102+
$current_batch_urls = array_slice($all_urls, $i * $work_batch_size, $work_batch_size);
103+
104+
if (!empty($current_batch_urls)) {
105+
// Execute multi-request for current batch
106+
$multi_request_contract = $this->http_multi_request_service->setMultiContract($current_batch_urls);
107+
108+
// Critical error: contract processing failed, stop update immediately
109+
if (!$multi_request_contract->process_done) {
110+
$error = !empty($multi_request_contract->error_msg) ? $multi_request_contract->error_msg : 'UNKNOWN ERROR';
111+
return $this->responseStopUpdate($error);
112+
}
113+
114+
// Handle failed downloads in this batch
115+
if (!empty($multi_request_contract->getFailedURLs())) {
116+
// Reduce batch size for retry if service suggests it
117+
if ($multi_request_contract->suggest_batch_reduce_to) {
118+
$on_repeat_batch_size = min($on_repeat_batch_size, $multi_request_contract->suggest_batch_reduce_to);
119+
}
120+
// Collect failed URLs for retry
121+
$download_again = array_merge($download_again, $multi_request_contract->getFailedURLs());
122+
}
123+
124+
// Write successfully downloaded content to files
125+
$write_result = $multi_request_contract->writeSuccessURLsContent($apbct->fw_stats['updating_folder']);
126+
127+
// File write error occurred, stop update
128+
if (is_string($write_result)) {
129+
return $this->responseStopUpdate($write_result);
130+
}
131+
132+
// Track successfully written URLs
133+
$written_urls = array_merge($written_urls, $write_result);
134+
}
135+
}
136+
137+
// Some downloads failed, schedule retry with adjusted batch size
138+
if (!empty($download_again)) {
139+
$apbct->fw_stats['multi_request_batch_size'] = $on_repeat_batch_size;
140+
$apbct->save('data');
141+
return $this->responseRepeatStage('FILES DOWNLOAD NOT COMPLETED, TRYING AGAIN', $download_again);
142+
}
143+
144+
// Verify all URLs were successfully downloaded and written
145+
if (empty(array_diff($all_urls, $written_urls))) {
146+
return $this->responseSuccess($direct_update);
147+
}
148+
149+
// Download incomplete with no retry - collect error information
150+
$last_contract_errors = isset($multi_request_contract) && $multi_request_contract->getContractsErrors()
151+
? $multi_request_contract->getContractsErrors()
152+
: 'no known contract errors';
153+
154+
$error = 'FILES DOWNLOAD NOT COMPLETED - STOP UPDATE, ERRORS: ' . $last_contract_errors;
155+
return $this->responseStopUpdate($error);
156+
}
157+
158+
/**
159+
* Creates error response to stop the update process
160+
*
161+
* @param string $message Error message describing why update was stopped
162+
*
163+
* @return array Error response array with 'error' key
164+
*/
165+
private function responseStopUpdate($message): array
166+
{
167+
$message = is_string($message) ? $message : $this->deafult_error_content;
168+
$message = $this->deafult_error_prefix . $message;
169+
return [
170+
'error' => $message
171+
];
172+
}
173+
174+
/**
175+
* Creates response to repeat current stage with modified arguments
176+
*
177+
* Used when downloads partially failed and should be retried with
178+
* potentially reduced batch size or different parameters.
179+
*
180+
* @param string $message Descriptive message about why stage needs repeating
181+
* @param array $args Arguments for retry attempt (typically failed URLs)
182+
*
183+
* @return array Response array with 'error' message and 'update_args' for retry
184+
*/
185+
private function responseRepeatStage($message, $args): array
186+
{
187+
$args = is_array($args) ? $args : [];
188+
$message = is_string($message) ? $message : $this->deafult_error_content;
189+
$message = $this->deafult_error_prefix . $message;
190+
return [
191+
'error' => $message,
192+
'update_args' => [
193+
'args' => $args
194+
],
195+
];
196+
}
197+
198+
/**
199+
* Creates success response to proceed to next stage or complete update
200+
*
201+
* @param bool $direct_update If true, returns simple boolean. If false, returns stage transition array.
202+
*
203+
* @return true|array True for direct update mode, or array with 'next_stage' key for staged updates
204+
*/
205+
private function responseSuccess($direct_update)
206+
{
207+
return $direct_update ? true : [
208+
'next_stage' => array(
209+
'name' => 'apbct_sfw_update__create_tables'
210+
)
211+
];
212+
}
213+
}

lib/Cleantalk/ApbctWP/Firewall/SFWUpdateHelper.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,4 +578,29 @@ public static function fallback()
578578
*/
579579
apbct_sfw_update__create_tables();
580580
}
581+
582+
public static function getSFWFilesBatchSize()
583+
{
584+
global $apbct;
585+
$work_batch_size = 10;
586+
587+
/**
588+
* Reduce batch size of curl multi instanced
589+
*/
590+
if (defined('APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE')) {
591+
if (
592+
is_int(APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE) &&
593+
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE > 1 &&
594+
APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE < 10
595+
) {
596+
$work_batch_size = APBCT_SERVICE__SFW_UPDATE_CURL_MULTI_BATCH_SIZE;
597+
};
598+
}
599+
600+
$work_batch_size = !empty($apbct->fw_stats['multi_request_batch_size'])
601+
? TT::toInt($apbct->fw_stats['multi_request_batch_size'], $work_batch_size)
602+
: $work_batch_size;
603+
604+
return $work_batch_size;
605+
}
581606
}

0 commit comments

Comments
 (0)