Skip to content
Merged
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
1 change: 1 addition & 0 deletions App/Views/create.volt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<div class="bar">
<div class="progress"></div>
</div>
<div class="label"></div>
</div>
</div>

Expand Down
78 changes: 75 additions & 3 deletions Lib/Backup.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Backup extends PbxExtensionBase
private string $result_dir;
private string $progress_file;
private string $progress_file_recover;
private string $stage_file;
private string $config_file;
private array $options;
private string $options_recover_file;
Expand Down Expand Up @@ -138,6 +139,7 @@ public function __construct($id, $options = null)
$this->config_file = "{$this->dirs['backup']}/{$this->id}/config.json";
$this->options_recover_file = "{$this->dirs['backup']}/{$this->id}/options_recover.json";
$this->progress_file_recover = "{$this->dirs['backup']}/{$this->id}/progress_recover.txt";
$this->stage_file = "{$this->dirs['backup']}/{$this->id}/stage.txt";

if (!is_array($options) && file_exists($this->config_file)) {
$this->options = json_decode(file_get_contents($this->config_file), true) ?? [];
Expand Down Expand Up @@ -725,13 +727,20 @@ public static function listBackups(string $backup_dir = ''): PBXApiResult
if (file_exists($config_file)) {
$config = json_decode(file_get_contents($config_file), true);
}
// Фаза выполнения: 'preparing' (формирование списка файлов) или '' (архивирование).
$stage = '';
$stageFile = "{$dirs['backup']}/{$base_filename}/stage.txt";
if (file_exists($stageFile)) {
$stage = trim((string)file_get_contents($stageFile));
}
// Вычислим timestamp.
$arr_fname = explode('_', $base_filename);
$data = [
'date' => preg_replace("/[^0-9+]/", '', $arr_fname[1] ?? '') ?: time(),
'size' => $size,
'progress' => $progress,
'total' => $total,
'stage' => $stage,
'config' => $config,
'pid' => $pid,
'id' => $base_filename,
Expand Down Expand Up @@ -1290,49 +1299,61 @@ private function getBatchSize(int $totalFiles): int
public function createArchive(): array
{
file_put_contents($this->errorFile, '');
// Переходим в фазу подготовки: сборка списка файлов и архивов модулей.
file_put_contents($this->stage_file, 'preparing');
if (file_exists($this->progress_file)) {
$file_data = file_get_contents($this->progress_file);
$data = explode('/', $file_data);
$this->progress = intval(trim($data[0]));
}else{
file_put_contents($this->progress_file, '0');
// Изначально показываем 0/1 — чтобы percent считался как 0, а не "не определён".
file_put_contents($this->progress_file, '0/1');
}
if(!$this->checkDiskSpace()){
$msg = 'There is not enough free disk space.';
file_put_contents($this->errorFile, $msg);
Util::sysLogMsg(__CLASS__, $msg);
@unlink($this->stage_file);
return ['result' => 'ERROR', 'message' => $msg];
}
if ( ! file_exists("{$this->dirs['backup']}/{$this->id}")) {
$msg = 'Unable to create directory for the backup.';
file_put_contents($this->errorFile, $msg);
Util::sysLogMsg(__CLASS__, $msg);
@unlink($this->stage_file);
return ['result' => 'ERROR', 'message' => $msg];
}
$result = $this->createFileList();
if ( ! $result) {
$msg = 'Unable to create file list. Failed to create file.';
file_put_contents($this->errorFile, $msg);
Util::sysLogMsg(__CLASS__, $msg);
@unlink($this->stage_file);
return ['result' => 'ERROR', 'message' => $msg];
}

if ( ! file_exists($this->file_list)) {
$msg = 'File list not found.';
file_put_contents($this->errorFile, $msg);
Util::sysLogMsg(__CLASS__, $msg);
@unlink($this->stage_file);
return ['result' => 'ERROR', 'message' => $msg];
}
$lines = file($this->file_list);
if ($lines === false) {
$msg = 'File list empty.';
file_put_contents($this->errorFile, $msg);
Util::sysLogMsg(__CLASS__, $msg);
@unlink($this->stage_file);
return ['result' => 'ERROR', 'message' => $msg];
}
$count_files = count($lines);
$batchSize = $this->getBatchSize($count_files);
file_put_contents($this->progress_file, "{$this->progress}/{$count_files}");
// Подготовка завершена — переходим в фазу архивации.
if (file_exists($this->stage_file)) {
unlink($this->stage_file);
}

$batchFiles = [];
while ($this->progress < $count_files) {
Expand Down Expand Up @@ -1424,7 +1445,7 @@ public function createArchive(): array
*
* @return string строки для flist.txt
*/
private function collectModuleFiles(): string
private function collectModuleFiles(int &$prepDone = 0, int $prepTotal = 1): string
{
$flist = '';
$customModulesDir = $this->dirs['custom_modules'];
Expand Down Expand Up @@ -1484,6 +1505,9 @@ private function collectModuleFiles(): string
}
}
}
// Отражаем прогресс фазы подготовки: модуль обработан.
$prepDone++;
file_put_contents($this->progress_file, "{$prepDone}/{$prepTotal}");
}
return $flist;
}
Expand Down Expand Up @@ -1681,10 +1705,30 @@ private function createFileList(): bool
return $result;
}

// Считаем общее число шагов подготовки для отображения прогресса.
// Шаги: модули (каждый = 1 tar.gz) + find по media + find по records.
$moduleCount = $this->countModuleCandidates();
$prepTotal = $moduleCount;
if (($this->options['backup-sound-files'] ?? '') === '1') {
$prepTotal++;
}
if (($this->options['backup-records'] ?? '') === '1' && $this->remote) {
$prepTotal++;
}
if ($prepTotal < 1) {
$prepTotal = 1;
}
$prepDone = 0;
file_put_contents($this->progress_file, "{$prepDone}/{$prepTotal}");

$flist = '';
if (($this->options['backup-config'] ?? '') === '1') {
file_put_contents($this->file_list, 'backup-config:' . $this->dirs['settings_db_path'] . "\n", FILE_APPEND);
$flist .= $this->collectModuleFiles();
$flist .= $this->collectModuleFiles($prepDone, $prepTotal);
} else {
// backup-config отключён — модули не сжимаем, но шаги учтены в prepTotal.
$prepDone += $moduleCount;
file_put_contents($this->progress_file, "{$prepDone}/{$prepTotal}");
}
if (($this->options['backup-cdr'] ?? '') === '1') {
file_put_contents($this->file_list, 'backup-cdr:' . $this->dirs['cdr_db_path'] . "\n", FILE_APPEND);
Expand All @@ -1699,6 +1743,8 @@ private function createFileList(): bool
foreach ($out as $filename) {
$flist .= 'backup-sound-files:' . $filename . "\n";
}
$prepDone++;
file_put_contents($this->progress_file, "{$prepDone}/{$prepTotal}");
}
if (($this->options['backup-records'] ?? '') === '1') {
if ($this->remote) {
Expand All @@ -1708,13 +1754,39 @@ private function createFileList(): bool
foreach ($out as $filename) {
$flist .= 'backup-records:' . $filename . "\n";
}
$prepDone++;
file_put_contents($this->progress_file, "{$prepDone}/{$prepTotal}");
}
}
file_put_contents($this->file_list, $flist, FILE_APPEND);

return true;
}

/**
* Подсчитывает количество кандидатов-модулей (каталог с module.json).
* Используется для расчёта прогресса фазы подготовки.
*/
private function countModuleCandidates(): int
{
$customModulesDir = $this->dirs['custom_modules'];
if (!is_dir($customModulesDir)) {
return 0;
}
$count = 0;
$entries = scandir($customModulesDir) ?: [];
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$moduleDir = "$customModulesDir/$entry";
if (is_dir($moduleDir) && file_exists("$moduleDir/module.json")) {
$count++;
}
}
return $count;
}

/**
* Добавляет батч обычных файлов в архив одной командой.
*
Expand Down
1 change: 1 addition & 0 deletions Messages/en.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
'bkp_SettingsRestoredWaitReboot' => 'Settings was restored',
'bkp_UploadError' => 'Failed to load backup file',
'bkp_StopCreateBackup' => 'Cancel backup',
'bkp_PreparingFileList' => 'Preparing',
'bkp_DeleteFiles' => 'Delete backup',
'bkp_RestoreBackupHeader' => 'Select restore options',
'bkp_CreateBackupHeader' => 'Select backup options',
Expand Down
1 change: 1 addition & 0 deletions Messages/ru.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
'bkp_SettingsRestoredWaitReboot' => 'Настройки восстановлены, станция перезагружается...',
'bkp_UploadError' => 'Не удалось загрузить файл бекапа',
'bkp_StopCreateBackup' => 'Отменить создание резервной копии',
'bkp_PreparingFileList' => 'Подготовка',
'bkp_DeleteFiles' => 'Удалить файл бекапа',
'bkp_RestoreBackupHeader' => 'Что необходимо восстановить?',
'bkp_CreateBackupHeader' => 'Что необходимо архивировать?',
Expand Down
16 changes: 8 additions & 8 deletions public/assets/js/backup-create-worker.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading