Перейти к содержанию

Конвертация файлов в PDF

Конвертация файлов в PDF

Рассмотрим задачу по конвертации файлов офисных форматов (doc, docx, xls, xlsx и др.) в файлы pdf.

Конвертация файла выполняется посредством создания и отслеживания отдельных процессов unoconv и LibreOffice. Хостинг процессов и управление их жизненным циклом выполняется фоновыми службами внутри веб-сервиса jinni. Запросы к jinni могут формироваться, основным сервисом web, так и плагином FileConverterPlugin сервиса chronos. Таким образом для конвертации файлов необходимо иметь установленные и запущенные сервисы chronos и jinni.

Из основного сервиса web можно вызвать API, которое отдаст операцию и опционально дождётся её завершения. При конвертации по умолчанию используются кэш файлов, и если запрошенный файл уже есть в кэше, то вызов API конвертации будет на 100% синхронный (мгновенный), в этом сценарии конвертация файла не выполняется.

Конвертация с ожиданием результата

Для выполнения конвертации файла с ожиданием результата необходимо:

  1. Получить зависимость Tessa.FileConverters.IFileConverter из Unity-контейнера, в котором требуется инициировать конвертацию в pdf.

  2. Вызвать метод-расширение для создания объекта-запроса на конвертацию (не создаёт операций): fileConverter.GetRequestOrThrowAsync(eventName, FileConverterFormat.Pdf, versionRowID), где eventName - строковая константа вашего события (например, "АлиасВашегоСобытия" или FileConverterEventNames.Unknown, если событие неизвестно).

  3. В полученном запросе можно установить флаги с настройками конвертации файлов (см. описание флагов в Tessa.FileConverters.FileConverterRequestFlags): request.Flags = ....

  4. Вызвать метод ConvertFileAsync, который получает запрос на конвертацию, созданный методом GetRequestOrThrowAsync, и возвращает результат сразу же, как только операция будет обработана.

  5. Вызвать метод GetStreamOrThrowAsync для полученного результата, чтобы получить содержимое сконвертированного файла: response.GetStreamOrThrowAsync(cancellationToken)).

// требуется только идентификатор версии файла Guid versionRowID = ...;

// обычно запрос из Unity выполняют через конструктор var fileConverter = unityContainer.Resolve<IFileConverter>();

var request = await fileConverter.GetRequestOrThrowAsync(FileConverterEventNames.Unknown, FileConverterFormat.Pdf, versionRowID, info: null, cancellationToken); var response = await fileConverter.ConvertFileAsync(request, cancellationToken);

await using (var stream = await response.GetStreamOrThrowAsync(cancellationToken)) { // stream содержит сконвертированный в PDF файл, копируем его в карточку, выгружаем и др. }

Конвертация без ожидания результата

Есть также механизмы для полностью асинхронной конвертации, при этом не задействует кэш файлов. Для выполнения конвертации файла с ожиданием результата необходимо:

  1. Получить зависимость Tessa.FileConverters.IFileConverter из Unity-контейнера, в котором требуется инициировать конвертацию в pdf;

  2. Вызвать метод-расширение для создания объекта-запроса на конвертацию (не создаёт операций): fileConverter.GetRequestOrThrowAsync(eventName, FileConverterFormat.Pdf, versionRowID), где eventName - строковая константа вашего события (например, "АлиасВашегоСобытия" или FileConverterEventNames.Unknown, если событие неизвестно).

  3. В полученном запросе установите флаги с настройками конвертации файлов для отключения кэша (см. описание флагов в Tessa.FileConverters.FileConverterRequestFlags): IgnoreCacheBeforeConversion, DoNotWaitResult, DoNotCacheResult, WithoutResponse.

  4. Напишите расширение-наследник FileConverterExtension, которое будет вызвано из плагина chronos при завершении конвертации. Регистрация расширения похожа на обычную регистрацию, используется интерфейс IFileConverterExtension, методы .WithOrder(AfterPlatform) и .WhenFileConverterEventNames("АлиасВашегоСобытия").

  5. В расширении получите контент сконвертированного файла и его размер из контекста и обработайте его необходимым образом: context.GetOutputContentAsync(cancellationToken).

В этом случае файл будет сконвертирован, помещён в контекст IFileConverterContext. Сразу после успешной конвертации операция удаляется из “активных операций”. Далее будет вызвано расширение IFileConverterExtension на указанное событие. Расширение получает файл из своего контекста и обрабатывает должным образом. Т.е. это сценарий, когда надо сконвертировать, выполнить постобработку и “забыть” об операции до момента её завершения, при этом конвертация может выполняться неопределённо длительное время.

Большой плюс этого подхода - отсутствует поток, ожидающий окончания конвертации и постоянно выполняющий проверки статуса операции. Этот подход несколько сложнее подхода с ожиданием операции, поскольку требует дополнительного расширения. Также не используется кэш, т.е. повторная конвертация того же файла будет выполняться заново.

Important

Расширения будут выполнены только если в запросе на конвертацию файла не был установлен флаг ExecutesSynchronously.

Расширение, выполняемое в веб-сервисе (например, CardStoreExtension), выглядит следующим образом:

// требуется только идентификатор версии файла Guid versionRowID = ...;

// обычно запрос из Unity выполняют через конструктор var fileConverter = unityContainer.Resolve<IFileConverter>();

var request = await fileConverter.GetRequestOrThrowAsync(FileConverterEventNames.Unknown, FileConverterFormat.Pdf, versionRowID, info: null, cancellationToken); request.Flags = request.Flags | FileConverterRequestFlags.IgnoreCacheBeforeConversion | FileConverterRequestFlags.DoNotWaitResult | FileConverterRequestFlags.DoNotCacheResult | FileConverterRequestFlags.WithoutResponse;

// любую дополнительную (сериализуемую) информацию можно передать через Info request.Info["CardID"] = Guid.NewGuid();

await fileConverter.ConvertFileAsync(request, cancellationToken);

Расширение для обработки результатов конвертации располагается в сборке Tessa.Extensions.Server. В зависимости от флага ExecutesSynchronously расширение может быть выполнено как в основном сервисе web, так и в сервисе chronos, когда конвертация будет завершена. Поэтому не забудьте также обновить файл со сборкой Tessa.Extensions.Server.dll в папке расширений для chronos.

public sealed class MyFileConverterExtension : FileConverterExtension { public override async Task AfterRequest(IFileConverterContext context) { if (!context.RequestIsSuccessful) { return; }

// переданная дополнительная информация доступна из контекста var cardID = context.Request.Info.TryGet<Guid?>("CardID");

var (stream, length) = await context.GetOutputContentAsync(context.CancellationToken); await using (stream) { // stream содержит сконвертированный в PDF файл, копируем его в карточку, выгружаем и др. } } }

Регистрация расширения в классе Registrator:

extensionContainer .RegisterExtension<IFileConverterExtension, MyFileConverterExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform, 1) .WhenFileConverterEventNames("MyCustomConversionToPdf")) ;

Если всё же требуется сохранить результат конвертации в кэш, то не устанавливайте в запросе на конвертацию флаг DoNotCacheResult и модифицируйте расширение.

// Получение контента сконвертированного файла var (stream, length) = await context.GetOutputContentAsync(context.CancellationToken); await using (stream) { // Обработка stream необходимым образом и запись его во временный файл ... var modifiedConvertedTempFile = TempFile.Acquire("MyFile");

// Так как результат может быть получен только один раз, то необходимо переопределить делегат для получения результата и вернуть другой поток var length = new FileInfo(modifiedConvertedTempFile.Path).Length; var stream = FileHelper.OpenRead(modifiedConvertedTempFile.Path); context.GetOutputContentAsync = _ => new ValueTask(stream, length);

// Если необходимо выполнить какой-либо метод обратного вызова при завершении конвертации, то его можно добавить в context context.FinalizationQueue.Enqueue(_ => modifiedConvertedTempFile.Dispose()) }

Back to top