Какой самый эффективный способ отображения изображений из ZIP-архива в автономном настольном приложении? - PullRequest
2 голосов
/ 14 октября 2019

У меня есть автономное Electron + React настольное приложение, которое не использует сервер для хранения данных. Вот ситуация:

Пользователь может создать некоторые текстовые данные и добавить к ним любое количество изображений. Когда они сохраняют данные, создается ZIP со следующей структурой:

myFile.zip
  ├ main.json // Text data
  └ images
     ├ 00.jpg
     ├ 01.jpg
     ├ 02.jpg
     └ ...

Если они хотят редактировать данные, они могут открыть сохраненное ZIP с помощью приложения.

Я использую ADM-ZIP для чтения содержимого ZIP. После открытия я отправляю данные JSON на Redux, так как они нужны мне для отображения интерфейса. Изображения, однако, не отображаются на компоненте, который читает архив, и могут находиться на нескольких «страницах» приложения (так что разные компоненты). Я отображаю их с компонентом Image, который берет реквизит name и возвращает тег img. Моя проблема здесь в том, как получить их src из архива.

Итак, вот мой вопрос: Какой может быть самый эффективный способ получения данных изображений, содержащихся в ZIPучитывая вышеописанную ситуацию?


Что я сейчас делаю:

Когда пользователь открывает свой файл, я извлекаю ZIP во временную папку и получаю изображенияпути оттуда. Временная папка удаляется только тогда, когда пользователь закрывает файл, а не когда он закрывает приложение (я не хочу изменять это поведение).

// When opening the archive
const archive = new AdmZip('myFiles.zip');
archive.extractAllTo(tempFolderPath, true);

// Image component
const imageSrc = `${tempFolderPath}/images/${this.props.name}`;

Проблема: если ZIP содержит много изображений, у пользователя должно быть достаточно места на диске для их правильного извлечения. Поскольку временная папка не обязательно удаляется при закрытии приложения, это означает, что место на диске будет занимать, пока они не закроют файл.


Что я пробовал:

  1. Сохранение данных ZIP в памяти

    Я попытался сохранить результат открытия ZIP в подпорке, а затем получить из него данные моих изображенийкогда они мне нужны:

    // When opening the archive
    const archive = new AdmZip('myFile.zip');
    this.props.saveArchive(archive); // Save in redux
    
    // Image component
    const imageData = this.props.archive.readFile(this.props.name);
    const imageSrc = URL.createObjectURL(new Blob([imageData], {type: "image/jpg"}));
    

    При этом процессе время загрузки изображений приемлемо.

    Проблема: с большим архивом, хранение архивных данных в памяти может быть плохо для исполнения (я думаю?), И я предполагаю, что существует ограничение на сколькоЯ могу хранить там.

  2. Повторное открытие ZIP с помощью ADM-ZIP каждый раз, когда мне нужно отобразить изображение

    // Image component
    const archive = new AdmZip('myFile.zip');
    const imageData = archive.readFile(this.props.name);
    const imageSrc = URL.createObjectURL(new Blob([imageData], {type: "image/jpg"}));
    

    Проблема: плохая производительность, очень медленно, когда у меня есть несколько изображений на одной странице.

  3. Хранение изображений Буфер /BLOB-объект в IndexedDB

    Проблема: он все еще хранится на диске, а его размер намного больше извлеченных файлов.

  4. Использование ZIP, который не сжимает данные

    Проблема: Я не увидел никакой разницы по сравнению со сжатым ZIP.


Что я рассмотрел:

Вместо ZIP я попытался найти тип файла, который мог бы действовать какне-компрессархив и быть прочитанным как каталог, но я не мог найти ничего подобного. Я также пытался создать собственный файл с помощью vanilla Node.js, но боюсь, что не знаю API файловой системы , достаточного для того, чтобы делать то, что я хочу.


У меня нет идей, поэтому любые предложения приветствуются. Может быть, я не видел самый очевидный способ сделать ... Или, может быть, то, что я сейчас делаю, не так уж плохо, и я переосмысливаю проблему.

Ответы [ 2 ]

4 голосов
/ 18 октября 2019

Что вы подразумеваете под «наиболее эффективным», не совсем понятно. Я сделаю некоторые предположения и отвечу заранее в соответствии с ними.


Решение для кэша

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

Obs¹: Поскольку вы рассматриваете нехватку дискового пространства как проблему, если вы хотите придерживаться этого, желательно, чтобы вы обрабатывали этот случай программно и давали пользователю некоторое предупреждение, поскольку у него мало свободного места в хранилище. критически важен.

TL; DR - Сначала расходуется, но позже - быстрее для повторного использования активов .

Ленивая загрузка

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

Obs¹: Это выглядит очень похоже на то, что вы сделали во время попытки № 2.

TL; DR - Быстрее при запуске, медленнее во время выполнения .

«Умная» загрузка

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

Пример:

  • Ленивая загрузка изображений для просмотра / страницы и хранение в кэш-памяти в памяти с ограниченным размером

  • Загрузка изображений в фоновом режиме припользователь перемещается


Теперь, независимо от вашего окончательного решения, не следует игнорировать следующие соображения:

  1. Память всегда будет быстрее записывать/ read than disk
  2. Частичное разархивирование (настройка определенных файлов) возможно во многих пакетах (включая ADM-ZIP) и всегда быстрее, чем разархивирование всего, особенно если размер ZIP-файла огромен.
  3. Использование IndexedDB или пользовательской базы данных на основе файлов, такой как SQLite, дает в целом хороший результат для огромного количества файлов и «умного» подхода, поскольку запросы и организацияАта легче через них.
  4. Всегда помните причину каждого выбора дизайна приложения, чем лучше вы понимаете, почему вы что-то делаете, тем лучше будет ваш конечный результат.
  5. Тема 4как говорится, на мой взгляд , в этом случае вы действительно немного обдумали, но это неплохо, и это действительно замечательно иметь такую ​​заботу о том, как вы можете делать вещи наилучшим образом,Я делаю это часто, даже если в этом нет необходимости, и это полезно для самосовершенствования.

Ну, я много написал: P

Я был бы очень рад, если бы все это помогло вамкаким-то образом.

Удачи!

TL; DR - нет закрытого ответа, основанного на вашем вопросе, это во многом зависит от контекста вашей заявки;некоторые из ваших попыток уже довольно хороши, но если вы приложите некоторые усилия для понимания контекста юзабилити для «разумной» обработки загрузки изображения, вы наверняка наградите вас.

0 голосов
/ 19 октября 2019

Проблема использования ZIP

Технически потоковая передача содержимого будет наиболее эффективным способом , но потоковая передача сжатого содержимого изнутри ZIP сложна и неэффективна, поскольку данные фрагментирован и должен проанализировать весь файл, чтобы получить центральный каталог

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

Стоимость использования 7zip

К счастью, вам нужно будет добавить бинарный файл 7zip в вашу программуон очень маленький ~ 2,5 МБ.

Как использовать 7zip

const { spawn } = require('child_process')
// Get all images from the zip or adjust to glob for the needed ones
// x, means extract while keeping the full path
// (optional instead of x) e, means extract flat
// Any additional parameter after the archive is a filter
let images = spawn('path/to/7zipExecutable', ['x', 'path/to/7zArchive', '*.png'])

Дополнительные соображения

Либо вы используете данные буфера и отображаете их внутри HTML через Base64 илиВы помещаете их в папку кэша, которая очищается в процессе извлечения. В зависимости от гранулярности вы хотите сделать это.

Имейте в виду, что вы должны использовать соответствующую папку для этого в Windows это будет %localappdata%/yourappname/cache в Linux и MacOS Я бы поместил это в ~/.yourappname/cache

Заключение

  • Потоковая передача данных является наиболее эффективной (самый низкий объем памяти / объем данных)
  • Используя собственный исполняемый файл, вы можете сжимать / распаковывать намного быстрее, чем с помощью чистого js
  • сжатие / декомпрессия происходит в другом процессе, предотвращающем блокировку вашего приложения при выполнении и рендеринг

ASAR в качестве альтернативы

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

Например, у вас есть ~/.myapp/save0001.asarв которой находится папка img с несколькими изображениями, доступ к которой можно получить просто ~/.myapp/save0001/img/x.png

...