Skip to content

Commit 74c1a9f

Browse files
Alexey PortnovAlexey Portnov
authored andcommitted
fix(sound-files): preserve uploads when ffmpeg conversion fails (#999)
The custom sound file upload pipeline could silently delete the user's original file when ffmpeg failed mid-conversion, leaving an orphan DB row that returned HTTP 422 on the Sound Files page. Defense-in-depth across four layers: - ConvertAudioFileAction: harden the unlink gate to require all requested formats produced non-empty output, compare paths via realpath() with a same-directory case-insensitive fallback, and emit a structured audit trail through SystemMessages so post-incident forensics can identify why the original was kept or removed. - SoundFilesConf::convertAudioFile: write each target into a sibling ".converting" tempfile and rename() onto the destination only after exit==0 && is_file && filesize>0. This eliminates the wav->wav in-place corruption window and guarantees the source survives a partial failure. - SoundFiles::afterDelete: add the missing 'opus' format to the cascade cleanup list (drift since 7a9d11a) and sweep stray ".converting" tempfiles left by interrupted workers. - PlaybackAction: return HTTP 410 Gone (instead of the default 422) when the audio file is missing or empty on disk, so the admin UI can render a clear "Audio file missing on disk, please re-upload" badge instead of a generic error. Frontend player marks the row, disables play, and reuses the same 410 contract for both HEAD metadata probes and GET playback. Also: regression tests for the unlink gate (with sentinel-stub ffmpeg to prove PATH override actually intercepts Util::which), the atomic-rename loop, and the cascade-delete sweep; new sf_AudioFileMissingWarning translation across all 26 supported locales.
1 parent 063e7f6 commit 74c1a9f

35 files changed

Lines changed: 815 additions & 28 deletions

File tree

sites/admin-cabinet/assets/js/pbx/SoundFiles/sound-files-index-player.js

Lines changed: 36 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sites/admin-cabinet/assets/js/src/SoundFiles/sound-files-index-player.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ class IndexSoundPlayer {
119119
headers
120120
})
121121
.then(response => {
122+
// 410 Gone: backend tells us the audio file is missing on disk while the DB
123+
// record still exists. Mark the row as broken so the user gets a clear hint
124+
// (and disables the play button) instead of being confused by a generic 422.
125+
if (response.status === 410) {
126+
this.markAsMissing();
127+
return;
128+
}
122129
if (!response.ok) {
123130
return;
124131
}
@@ -139,6 +146,30 @@ class IndexSoundPlayer {
139146
});
140147
}
141148

149+
/**
150+
* Mark this player row as having a missing/broken audio file.
151+
* Disables the play button, shows a warning icon and a tooltip explaining what to do.
152+
*/
153+
markAsMissing() {
154+
const $row = $(`#${this.id}`);
155+
if ($row.hasClass('audio-file-missing')) {
156+
return;
157+
}
158+
$row.addClass('audio-file-missing');
159+
const tooltipText = (typeof globalTranslate !== 'undefined'
160+
&& globalTranslate.sf_AudioFileMissingWarning)
161+
? globalTranslate.sf_AudioFileMissingWarning
162+
: 'Audio file is missing on disk, please re-upload';
163+
this.$pButton
164+
.prop('disabled', true)
165+
.addClass('disabled')
166+
.attr('title', tooltipText)
167+
.find('i')
168+
.removeClass('play pause')
169+
.addClass('exclamation triangle');
170+
this.$spanDuration.text('--:--');
171+
}
172+
142173
/**
143174
* Callback for metadata loaded event.
144175
*/
@@ -316,6 +347,14 @@ class IndexSoundPlayer {
316347
// Fetch audio file with authentication
317348
fetch(fullUrl, { headers })
318349
.then(response => {
350+
if (response.status === 410) {
351+
this.markAsMissing();
352+
const friendly = (typeof globalTranslate !== 'undefined'
353+
&& globalTranslate.sf_AudioFileMissingWarning)
354+
? globalTranslate.sf_AudioFileMissingWarning
355+
: 'Audio file is missing on disk, please re-upload';
356+
throw new Error(friendly);
357+
}
319358
if (!response.ok) {
320359
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
321360
}

src/Common/Messages/az/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,5 @@
957957
'cf_EnterSearchPhrase' => 'Axtarış məlumatlarınızı daxil edin',
958958
'st_tooltip_note' => 'Qeyd',
959959
'sf_AudioFileLoadError' => 'Audio faylı yüklənərkən xəta baş verdi',
960+
'sf_AudioFileMissingWarning' => 'Səs faylı diskdə yoxdur. Yenidən yükləyin.',
960961
];

src/Common/Messages/cs/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,5 @@
957957
'cdr_DeleteFailed' => 'Nepodařilo se smazat položku',
958958
'cdr_NoPermissionToDelete' => 'Nemáte oprávnění k mazání příspěvků.',
959959
'sf_AudioFileLoadError' => 'Chyba při načítání audio souboru',
960+
'sf_AudioFileMissingWarning' => 'Zvukový soubor chybí na disku. Nahrajte jej znovu.',
960961
];

src/Common/Messages/da/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,5 @@
957957
'st_tooltip_note' => 'Note',
958958
'sf_CsvCategory' => 'CSV-filer',
959959
'sf_AudioFileLoadError' => 'Fejl ved indlæsning af lydfil',
960+
'sf_AudioFileMissingWarning' => 'Lydfilen mangler på disken. Upload den venligst igen.',
960961
];

src/Common/Messages/de/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@
619619
// SF
620620
'sf_AddNewSoundFile' => 'Neue Audio-Datei',
621621
'sf_AudioFileLoadError' => 'Fehler beim Laden der Audiodatei',
622+
'sf_AudioFileMissingWarning' => 'Audiodatei fehlt auf dem Datenträger. Bitte laden Sie sie erneut hoch.',
622623
'sf_ColumnFile' => 'Dateiname',
623624
'sf_ColumnPlayer' => 'Abspielen',
624625
'sf_CustomSounds' => 'Audiodateien',

src/Common/Messages/el/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
// SF
561561
'sf_AddNewSoundFile' => 'Προσθήκη νέου αρχείου ήχου',
562562
'sf_AudioFileLoadError' => 'Σφάλμα κατά τη φόρτωση αρχείου ήχου',
563+
'sf_AudioFileMissingWarning' => 'Το αρχείο ήχου λείπει από τον δίσκο. Παρακαλούμε ανεβάστε το ξανά.',
563564
'sf_ColumnFile' => 'Ονομα',
564565
'sf_ColumnPlayer' => 'Παίχτης',
565566
'sf_CustomSounds' => 'Αρχεία ήχου',

src/Common/Messages/en/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@
668668
'sf_AddNewAudioFile' => 'Add new audio file',
669669
'sf_AddNewSoundFile' => 'Add new sound file',
670670
'sf_AudioFileLoadError' => 'Error loading audio file',
671+
'sf_AudioFileMissingWarning' => 'Audio file is missing on disk. Please re-upload it.',
671672
'sf_ColumnFile' => 'Name',
672673
'sf_ColumnPlayer' => 'Player',
673674
'sf_CustomSounds' => 'Audio files',

src/Common/Messages/es/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@
570570
// SF
571571
'sf_AddNewSoundFile' => 'Agregar nuevo archivo de sonido',
572572
'sf_AudioFileLoadError' => 'Error al cargar el archivo de audio',
573+
'sf_AudioFileMissingWarning' => 'El archivo de audio falta en el disco. Por favor, vuelva a subirlo.',
573574
'sf_ColumnFile' => 'Nombre',
574575
'sf_ColumnPlayer' => 'Jugador',
575576
'sf_CustomSounds' => 'Archivos de sonido',

src/Common/Messages/fi/Common.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@
642642
'sf_AddNewAudioFile' => 'Lisää uusi äänitiedosto',
643643
'sf_AddNewSoundFile' => 'Lisää uusi äänitiedosto',
644644
'sf_AudioFileLoadError' => 'Virhe ladattaessa äänitiedostoa',
645+
'sf_AudioFileMissingWarning' => 'Äänitiedosto puuttuu levyltä. Lataa se uudelleen.',
645646
'sf_ColumnFile' => 'Nimi',
646647
'sf_ColumnPlayer' => 'Soitin',
647648
'sf_CustomSounds' => 'Äänitiedostot',

0 commit comments

Comments
 (0)