Files

9.1 KiB
Raw Permalink Blame History

Справочник кода Upload Platform

Документ описывает актуальную picker-only реализацию. VM upload, backend sessions и API загрузки относятся к отменённой архитектуре и в текущем коде отсутствуют.

Архитектура

site/app.py
  -> site/templates/index.html
      -> dist/file-picker.iife.js
          -> upload/frontend/index.js
          -> table/on_files_change.js
          -> table/on_folder_change.js
          -> table/add_file_with_dedup.js
          -> table/render.js
          -> zip/list_zip_files.js

Flask отдаёт страницу и готовый bundle. Все выбранные файлы остаются в памяти браузера; сервер не принимает содержимое файлов.

Модель данных

Каждый узел дерева имеет поля:

Поле Тип Назначение
id string Уникальный идентификатор для DOM-кнопок и поиска.
kind file, folder, zip Тип узла. file является leaf.
name string Имя отображения в дереве.
path string Полный логический путь внутри выбора/архива.
file File | null Исходный или созданный из ZIP browser File для leaf.
children Array Дочерние узлы группы.
expanded boolean Видимость дочерних строк в таблице.

Внутреннее состояние picker-а:

Поле Назначение
nodes Корневые узлы дерева.
fileKeys Set ключей path + NUL + size для дедупликации.
busy Защита от параллельной обработки async change-событий.

upload/frontend/table

add_file_with_dedup.js

Функция Вход Выход Побочные эффекты
addFileWithDedup(state, fileNode) state и корневой file/folder/zip-узел boolean Фильтрует дубли и пустые группы, добавляет узел и ключи в state.

Дедупликация выполняется рекурсивно. Один и тот же путь с другим размером не считается дублем.

esc.js

Функция Вход Выход
esc(value) Любое значение HTML-экранированная строка

Используется перед innerHTML, data-path, aria-label и текстом имени.

fs.js

Функция Вход Выход
fs(bytes) Размер в байтах Строка в B, KB или MB

rebase_tree.js

Функция Вход Выход Побочные эффекты
rebaseTree(root, prefix) Дерево и префикс папки То же дерево Рекурсивно меняет path; для leaf сохраняет базовое имя File.name.

Модуль является единственной реализацией rebasing для ZIP внутри выбранной папки и для nested ZIP.

on_files_change.js

Функция Вход Выход Назначение
addFiles(state, cfg, files, elements) state, allowedExt, FileList/Array, DOM Promise<void> Фильтрует обычные файлы, раскрывает ZIP, вызывает render.
onFilesChange(state, cfg, elements) state, config, DOM async callback Блокирует повторные события через busy и вызывает addFiles.

Ошибка отдельного ZIP пропускается и передаётся в cfg.onError(error, file), если callback задан. finally обязательно освобождает busy.

on_folder_change.js

Функция Вход Выход Назначение
onFolderChange(state, cfg, elements) state, allowedExt, folder input и DOM async callback Строит дерево по webkitRelativePath, раскрывает ZIP и рендерит.

Корень пути становится folder-узлом. Вложенные папки создаются по сегментам пути, затем корень проходит общую дедупликацию.

render.js

Функция Вход Выход Назначение
renderNode(node, depth, cfg) Узел, глубина и render config HTML Рекурсивно создаёт строки дерева. Внутренняя функция.
render(state, elements, cfg) state, DOM и render config void Перерисовывает таблицу и счётчик.
findNode(nodes, id) Массив узлов и id {node, nodes} или null Ищет узел и его родительский массив.
flattenFiles(nodes, result) Дерево и аккумулятор Массив leaf-файлов Преобразует дерево для API приложения.

render считает все leaf-файлы, включая свернутые группы. renderNode показывает дочерние строки только при expanded === true.

upload/frontend/zip

list_zip_files.js

Функция Вход Выход Назначение
extensionAllowed(name, allowedExt) Имя и расширения boolean Проверяет расширение. Внутренняя.
makeFile(data, name) Байты и путь File Создаёт browser File с базовым именем. Внутренняя.
safeEntryParts(entryName) Сырой ZIP-путь сегменты или null Отбрасывает traversal и опасные пути. Внутренняя.
node(kind, name, path, children, file) Метаданные узла node Создаёт узел. Внутренняя.
addPath(root, parts, fileNode) Дерево, сегменты, узел void Создаёт папки и вставляет узел. Внутренняя.
listEntries(data, zipName, allowedExt, depth) ZIP bytes и контекст `Promise<node null>`
listZipFiles(file, allowedExt, limits) browser File, расширения и limits `Promise<node null>`

Безопасность: отклоняются абсолютные пути, backslash, пустые сегменты, . и ..; применяются лимиты maxEntries, maxTotalBytes, maxEntryBytes и maxDepth с безопасными значениями по умолчанию. ZIP без разрешённых leaf-файлов возвращает null и не создаёт пустую группу.

upload/frontend/index.js

initFilePicker({ mount, allowedExt, labels, layout, limits, onChange, onError }) строит весь DOM внутри HTMLElement или CSS-селектора mount, подключает inputs, таблицу и обработчики. Возвращаемый API: pickFiles, pickFolder, addFiles, getFiles, remove, clear, render, destroy. getFiles и onChange возвращают копии массива leaf-файлов. destroy снимает слушатели и очищает mount.

IIFE-бандл экспортирует этот API как FilePicker.initFilePicker; ESM-бандл экспортирует функцию напрямую. fflate включён в оба bundle, поэтому глобальный vendor script не требуется.

Flask и HTML

site/app.py

Маршрут/объект Вход Выход
CONFIG config.json JSON-конфигурация picker
VERSION константа Версия страницы
index() / / HTTP GET HTML с version и config
health() / /health HTTP GET ok, HTTP 200
upload_frontend(filename) HTTP GET и относительный путь Совместимый маршрут для исходных frontend-модулей
file_picker_dist(filename) HTTP GET и имя bundle Bundle из dist

site/templates/index.html

Шаблон создаёт mount, подключает IIFE bundle с query-параметром версии для сброса browser cache и вызывает FilePicker.initFilePicker.

Vendor site/static/vendor/fflate.min.js не документируется построчно: это внешняя библиотека, используемая как готовый runtime dependency.