Use shared file publication in video processing

This commit is contained in:
evgeny committed 2026-10-22 10:15:39 +02:00
1 parent 3ed4b73ebf
commit 6355bd1d73
2 files changed
+16 -155

No files matched your search

+2
View File
@@ -15,6 +15,8 @@ libuasync_a_SOURCES = \
socket_compat.h \
platform_compat.c \
platform_compat.h \
file_io.c \
file_io.h \
getmyip.c \
getmyip.h \
swm_min.c \
+14 -155
View File
@@ -5,6 +5,7 @@
#include "../../lib/mem.h"
#include "../../lib/u_async.h"
#include "../../lib/platform_compat.h"
#include "../../lib/file_io.h"
#include <stdio.h>
#include <stdlib.h>
@@ -232,148 +233,6 @@ int video_needs_transcode(const struct video_info* info) {
return 0;
}
struct vc_file {
char temporary[VIDEO_PATH_MAX + 64];
FILE* file;
};
#ifdef _WIN32
static int vc_wide(const char* path, wchar_t* wide) {
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, VIDEO_PATH_MAX + 64)) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid UTF-8 path=%s winerr=%lu", path, GetLastError());
return -1;
}
#endif
/* Проверить обычные файлы и их идентичность до записи; повторить перед публикацией. */
static int vc_paths(const char* source, const char* destination) {
if (!source || !destination || !*source || !*destination ||
strlen(source) >= VIDEO_PATH_MAX || strlen(destination) >= VIDEO_PATH_MAX) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid/oversized source or destination");
return -1;
}
#ifdef _WIN32
wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64];
if (vc_wide(source, from) || vc_wide(destination, to)) return -1;
HANDLE input = CreateFileW(from, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
BY_HANDLE_FILE_INFORMATION a, b;
int valid = input != INVALID_HANDLE_VALUE && GetFileInformationByHandle(input, &a) &&
GetFileType(input) == FILE_TYPE_DISK && !(a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
if (input != INVALID_HANDLE_VALUE) CloseHandle(input);
if (!valid) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video invalid source=%s winerr=%lu", source, GetLastError()); return -1; }
DWORD attributes = GetFileAttributesW(to);
if (attributes == INVALID_FILE_ATTRIBUTES) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination inspection failed path=%s winerr=%lu", destination, error);
return -1;
}
if (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is directory/reparse point path=%s", destination); return -1;
}
HANDLE output = CreateFileW(to, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
valid = output != INVALID_HANDLE_VALUE && GetFileInformationByHandle(output, &b) && GetFileType(output) == FILE_TYPE_DISK;
if (output != INVALID_HANDLE_VALUE) CloseHandle(output);
if (!valid || (a.dwVolumeSerialNumber == b.dwVolumeSerialNumber &&
a.nFileIndexHigh == b.nFileIndexHigh && a.nFileIndexLow == b.nFileIndexLow)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination aliases source or cannot be inspected path=%s", destination); return -1;
}
#else
struct stat input, output;
if (stat(source, &input) || !S_ISREG(input.st_mode)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video source is not readable regular file path=%s errno=%d", source, errno); return -1;
}
if (lstat(destination, &output)) {
if (errno == ENOENT) return 0;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination stat failed path=%s errno=%d", destination, errno); return -1;
}
if (!S_ISREG(output.st_mode) || (input.st_dev == output.st_dev && input.st_ino == output.st_ino)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video destination is not distinct regular file source=%s destination=%s", source, destination);
return -1;
}
#endif
return 0;
}
/* Создать уникальный соседний файл эксклюзивно; FILE остаётся у задачи до finish/discard. */
static int vc_file_open(struct vc_file* output, const char* destination) {
for (int attempt = 0; attempt < 8; attempt++) {
uint8_t random[16]; char suffix[33];
if (random_bytes(random, sizeof(random))) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temp randomness failed"); return -1; }
for (unsigned i = 0; i < sizeof(random); i++) snprintf(suffix + i * 2, 3, "%02x", random[i]);
snprintf(output->temporary, sizeof(output->temporary), "%s.tmp.%s", destination, suffix);
#ifdef _WIN32
wchar_t wide[VIDEO_PATH_MAX + 64];
if (vc_wide(output->temporary, wide)) { output->temporary[0] = 0; return -1; }
int fd = _wopen(wide, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
#else
int fd = open(output->temporary, O_CREAT | O_EXCL | O_RDWR, 0600);
#endif
if (fd < 0) {
output->temporary[0] = 0;
if (errno == EEXIST) continue;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary creation failed destination=%s errno=%d", destination, errno);
return -1;
}
#ifdef _WIN32
output->file = _fdopen(fd, "wb+");
if (!output->file) _close(fd);
#else
output->file = fdopen(fd, "wb+");
if (!output->file) close(fd);
#endif
if (!output->file) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video fdopen failed errno=%d", errno); return -1; }
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video temporary opened path=%s", output->temporary);
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video temporary collision limit destination=%s", destination);
return -1;
}
static int vc_file_finish(struct vc_file* output) {
int error = fflush(output->file) ? errno : 0;
if (fclose(output->file) && !error) error = errno;
output->file = NULL;
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video flush/close failed path=%s errno=%d", output->temporary, error); return -1; }
return 0;
}
static void vc_file_discard(struct vc_file* output) {
if (output->file) {
if (fclose(output->file)) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video cleanup close failed errno=%d", errno);
output->file = NULL;
}
if (!output->temporary[0]) return;
#ifdef _WIN32
wchar_t wide[VIDEO_PATH_MAX + 64];
int error = vc_wide(output->temporary, wide) ? -1 : _wremove(wide);
#else
int error = remove(output->temporary);
#endif
if (error && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "video temporary cleanup failed path=%s errno=%d", output->temporary, errno);
output->temporary[0] = 0;
}
/* Последняя способная отказать операция: после rename результат уже опубликован. */
static int vc_file_publish(struct vc_file* output, const char* source, const char* destination) {
if (vc_paths(source, destination)) return -1;
#ifdef _WIN32
wchar_t from[VIDEO_PATH_MAX + 64], to[VIDEO_PATH_MAX + 64];
if (vc_wide(output->temporary, from) || vc_wide(destination, to)) return -1;
if (!MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s winerr=%lu", destination, GetLastError()); return -1;
}
#else
if (rename(output->temporary, destination)) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video publish failed destination=%s errno=%d", destination, errno); return -1;
}
#endif
output->temporary[0] = 0;
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "video published destination=%s", destination);
return 0;
}
struct vc_stream {
int index;
@@ -391,7 +250,7 @@ struct vc_stream {
struct vc_ctx {
char source[VIDEO_PATH_MAX], destination[VIDEO_PATH_MAX];
struct vc_file file;
struct file_io_temp file;
void (*done)(void*, int, const struct video_info*);
void* arg;
int error, remux;
@@ -409,7 +268,7 @@ static int vc_io_write(void* opaque, const uint8_t* bytes, int length) {
#else
static int vc_io_write(void* opaque, uint8_t* bytes, int length) {
#endif
struct vc_file* file = opaque;
struct file_io_temp* file = opaque;
if (fwrite(bytes, 1, length, file->file) == (size_t)length) return length;
int error = errno ? errno : EIO;
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 write failed path=%s requested=%d errno=%d", file->temporary, length, error);
@@ -417,7 +276,7 @@ static int vc_io_write(void* opaque, uint8_t* bytes, int length) {
}
static int64_t vc_io_seek(void* opaque, int64_t offset, int whence) {
struct vc_file* file = opaque;
struct file_io_temp* file = opaque;
if (whence == AVSEEK_SIZE) {
#ifdef _WIN32
struct _stat64 info;
@@ -459,7 +318,7 @@ static int vc_output_close(struct vc_ctx* ctx) {
avio_context_free(&ctx->output->pb);
if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video MP4 buffered output failed error=%s", vc_errstr(error));
}
if (ctx->file.file && vc_file_finish(&ctx->file)) error = -1;
if (ctx->file.file && file_io_temp_finish(&ctx->file)) error = -1;
return error < 0 ? -1 : 0;
}
@@ -854,7 +713,7 @@ static int vc_run(struct vc_ctx* ctx) {
static void vc_thread_entry(void* raw) {
struct vc_ctx* ctx = raw;
video_ffmpeg_init();
if (vc_paths(ctx->source, ctx->destination) || vc_file_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; }
if (file_io_paths(ctx->source, ctx->destination) || file_io_temp_open(&ctx->file, ctx->destination)) { ctx->error = -1; return; }
struct stat info;
if (!stat(ctx->source, &info)) ctx->source_size = info.st_size;
int error = vc_run(ctx);
@@ -873,8 +732,8 @@ static void vc_thread_entry(void* raw) {
static void vc_done_trampoline(void* raw, int error) {
struct vc_ctx* ctx = raw;
if (!error) error = ctx->error;
if (!error && vc_file_publish(&ctx->file, ctx->source, ctx->destination)) error = -1;
vc_file_discard(&ctx->file);
if (!error && file_io_temp_publish(&ctx->file, ctx->source, ctx->destination)) error = -1;
file_io_temp_discard(&ctx->file);
vc_progress_set(-1);
vc_active_set(0);
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "video completion destination=%s error=%d", ctx->destination, error);
@@ -929,11 +788,11 @@ static int vc_first_frame(AVFormatContext* format, AVCodecContext* decoder, int
}
int video_extract_thumbnail(const char* src, const char* dst, int max_w) {
if (vc_paths(src, dst)) return -1;
if (file_io_paths(src, dst)) return -1;
if (max_w <= 0) max_w = 320;
if (max_w < 2) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail width must be >=2"); return -1; }
video_ffmpeg_init();
struct vc_file output = {0};
struct file_io_temp output = {0};
AVFormatContext* format = NULL;
AVCodecContext* decoder = NULL; AVCodecContext* encoder = NULL;
AVFrame* input = NULL; AVFrame* frame = NULL; AVPacket* packet = NULL;
@@ -983,17 +842,17 @@ int video_extract_thumbnail(const char* src, const char* dst, int max_w) {
}
if (error < 0) goto done;
phase = "write JPEG";
if (vc_file_open(&output, dst)) { error = AVERROR(EIO); goto done; }
if (file_io_temp_open(&output, dst)) { error = AVERROR(EIO); goto done; }
if (fwrite(packet->data, 1, packet->size, output.file) != (size_t)packet->size) {
error = AVERROR(errno ? errno : EIO); goto done;
}
if (vc_file_finish(&output)) { error = AVERROR(EIO); goto done; }
if (file_io_temp_finish(&output)) { error = AVERROR(EIO); goto done; }
phase = "publish JPEG";
error = vc_file_publish(&output, src, dst) ? AVERROR(EIO) : 0;
error = file_io_temp_publish(&output, src, dst) ? AVERROR(EIO) : 0;
done:
if (error < 0) DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "video thumbnail failed phase=%s source=%s destination=%s error=%s",
phase, src, dst, vc_errstr(error));
vc_file_discard(&output);
file_io_temp_discard(&output);
sws_freeContext(scale); av_frame_free(&input); av_frame_free(&frame); av_packet_free(&packet);
avcodec_free_context(&decoder); avcodec_free_context(&encoder); avformat_close_input(&format);
return error < 0 ? -1 : 0;