8e389c03f8
Реализовать двухпанельный Commander от платформенного PoC до этапов P6-P20: EMM-каталог, сортировку и выбор, операции с файлами и деревьями, транзакционное копирование, метаданные, политику конфликтов и предварительную проверку свободного места. Добавить проектную документацию, HDD/MAME-сценарии и проверенные артефакты. Расширить libc операцией bank_write_page, исправлением режима O_RDONLY и связанными регрессионными проверками.
679 lines
40 KiB
Markdown
679 lines
40 KiB
Markdown
# Sprinter Commander: архитектура
|
||
|
||
Статус: P1/P2 подтверждены; одностраничная модель панели P3 реализована,
|
||
проходит граничные проверки DSS
|
||
|
||
Связанные документы:
|
||
[требования](commander-requirements.md),
|
||
[клавиши и команды](commander-keymap.md),
|
||
[план разработки](commander-roadmap.md),
|
||
[платформенные пробы P1](p1-platform-probes.md),
|
||
[экранный backend P2](p2-screen-backend.md),
|
||
[результаты P2.1](p2-screen-probe-results.md),
|
||
[результаты skeleton P2](p2-skeleton-results.md)
|
||
|
||
## 1. Цели архитектуры
|
||
|
||
Архитектура должна позволить получить небольшой технический PoC, не создавая
|
||
тупиков для полной версии. Главные ограничения задаются не исходным VC, а
|
||
платформой Sprinter:
|
||
|
||
- Z80 имеет 64-КБ адресное пространство;
|
||
- W0 занят DSS/BIOS;
|
||
- стек обязан находиться в W2;
|
||
- полный список двух панелей не помещается вместе с кодом в near-память;
|
||
- код полной версии потребует банков;
|
||
- DSS имеет критический предел в восемь открытых файлов;
|
||
- текстовый экран удобно восстанавливать непосредственно из EMM-страницы.
|
||
|
||
## 2. Принятые архитектурные решения
|
||
|
||
### ADR-001. Новая реализация, а не перенос
|
||
|
||
Commander пишется на C для SDCC. VC 4.05/4.99 используются как описание
|
||
поведения и состава функций, Dos Navigator — как источник архитектурных
|
||
идей. Зависимости от DOS interrupt API, сегментной модели x86, PSP, DTA,
|
||
TSR, overlays, XMS и EMS в код проекта не переносятся.
|
||
|
||
### ADR-002. Лёгкий цикл событий вместо оконного framework
|
||
|
||
Приложение имеет один цикл событий, набор символьных команд и явное состояние
|
||
экрана. Иерархия объектов наподобие Turbo Vision не используется.
|
||
|
||
Причины:
|
||
|
||
- меньше резидентного кода и данных;
|
||
- нет виртуальных методов и потоковой сериализации объектов;
|
||
- проще анализировать банковские вызовы;
|
||
- легче гарантировать освобождение ресурсов.
|
||
|
||
### ADR-003. Целевой режим памяти — `big`
|
||
|
||
PoC собирается с `--memory big --safe`:
|
||
|
||
- W0 `0x0000–0x3FFF` — DSS/BIOS;
|
||
- W1 `0x4000–0x7FFF` — переключаемые банки кода;
|
||
- W2 `0x8000–0xBFFF` — HOME, DATA/BSS, heap и стек;
|
||
- W3 `0xC000–0xFFFF` — EMM-страницы данных.
|
||
|
||
Если первоначальный skeleton временно собирается без банков, это не меняет
|
||
контрактов модулей. До принятия PoC должна быть проверена именно сборка `big`.
|
||
|
||
### ADR-004. Панели, экран и рабочая область копирования хранятся в EMM
|
||
|
||
Начиная с P4 PoC выделяет один блок из четырёх последовательных 16-КБ страниц.
|
||
P2/P3 использовали первые три страницы; четвёртая заранее резервируется текущим
|
||
`ScApp`, чтобы копирование не увеличивало W2 и не меняло схему памяти во время
|
||
операции:
|
||
|
||
| Логическая страница | Назначение | Использование |
|
||
|---:|---|---|
|
||
| 0 | записи левой панели | до 640 `ScDirEntry`, 1024 байта резерва |
|
||
| 1 | записи правой панели | до 640 `ScDirEntry`, 1024 байта резерва |
|
||
| 2 | экранный буфер | первые 5120 байт |
|
||
| 3 | рабочая область copy job | первые 4096 байт в PoC |
|
||
|
||
Допускается выделение четырёх отдельных блоков, если эксперимент покажет
|
||
проблему с последовательным блоком, но внешний интерфейс хранилища от этого
|
||
не меняется.
|
||
|
||
### ADR-005. Запрещены долгоживущие указатели на W3
|
||
|
||
Указатель в диапазон `0xC000–0xFFFF` действителен только внутри функции,
|
||
которая явно владеет отображением W3. После выхода из неё указатель считается
|
||
недействительным.
|
||
|
||
Ни `PanelState`, ни `ScApp`, ни длительная операция не содержат near-указатель
|
||
на содержимое EMM. Они хранят физическую страницу и смещение.
|
||
|
||
### ADR-006. Экран строится как модель ячеек
|
||
|
||
Каждая экранная ячейка представлена двумя байтами `(символ, атрибут)`. Формат
|
||
совпадает с подтверждённым форматом ESTEX `WINREST`:
|
||
|
||
```c
|
||
typedef struct {
|
||
uint8_t ch;
|
||
uint8_t attr;
|
||
} ScCell;
|
||
```
|
||
|
||
Полный экран занимает `80 * 32 * 2 = 5120` байт. Renderer изменяет EMM-буфер,
|
||
помечает строки грязными, а backend выводит либо диапазон строк, либо весь
|
||
экран. Backend использует координатные ESTEX/BIOS-функции; основной пакетный
|
||
путь — проверенный `WINREST`. Прямой доступ к VRAM не используется.
|
||
|
||
Renderer использует смысловые роли атрибутов (`frame`, `title`, `active`,
|
||
`passive`, `error`), а не числа палитры. Модуль `sc_theme`, подтверждённый
|
||
пробой P2.2, назначает ролям разные атрибуты, сохраняет затрагиваемые записи
|
||
четырёх планов и восстанавливает их при cleanup. Это не меняет формат
|
||
`ScCell` или API renderer.
|
||
Системные BIOS/ESTEX-вызовы палитры разрешены; запрещён только обход системного
|
||
экранного API через прямую адресацию VRAM и оконные дескрипторы BIOS.
|
||
|
||
Дескрипторы BIOS `WIN_OPEN/WIN_CLOSE/WIN_*_WIN` не используются: BIOS хранит
|
||
только один описатель окна 0. Слово `WIN` в ESTEX `WINREST` означает область
|
||
экрана и не делает эту функцию частью BIOS window manager.
|
||
|
||
### ADR-007. Источник файлов отделён от панели
|
||
|
||
Панель не вызывает `ffirst/fnext` напрямую. Она работает через модуль
|
||
источника. В PoC реализован только `SC_SOURCE_REAL_FS`.
|
||
|
||
Вместо таблицы виртуальных функций используется `kind` и обычный диспетчер:
|
||
|
||
```c
|
||
typedef enum {
|
||
SC_SOURCE_REAL_FS = 0,
|
||
SC_SOURCE_SEARCH,
|
||
SC_SOURCE_ARCHIVE
|
||
} ScSourceKind;
|
||
```
|
||
|
||
Такой подход не создаёт near/far function pointers и упрощает размещение
|
||
реализаций по банкам. `SEARCH` и `ARCHIVE` до соответствующих этапов должны
|
||
возвращать внутреннюю ошибку `SC_ERR_UNSUPPORTED`, которая на границе
|
||
платформы при необходимости отображается как ESTEX `EUNKOP`.
|
||
|
||
### ADR-008. Длительные операции являются jobs
|
||
|
||
Копирование, а позднее удаление, поиск и подсчёт размеров, оформляются как
|
||
конечные автоматы:
|
||
|
||
```text
|
||
INIT -> PREPARE -> RUN_CHUNK -> PRE_COMMIT -> COMMIT -> DONE
|
||
| | |
|
||
+-> CANCEL ----+-----------+
|
||
+-> ERROR -----+-----------+
|
||
```
|
||
|
||
Один шаг не должен надолго захватывать управление. Между блоками приложение
|
||
может обновить прогресс и проверить отмену. Даже последний успешно записанный
|
||
блок завершает только `RUN_CHUNK`: отдельный вызов переводит job через
|
||
`PRE_COMMIT` к атомарному rename. Поэтому `Esc`, полученный после отображения
|
||
`N/N`, всё ещё успевает закрыть и удалить temp до появления целевого файла.
|
||
|
||
### ADR-009. Viewer, editor и setup сначала отдельные программы
|
||
|
||
Основной Commander отвечает за панели и операции с файлами. Просмотрщик,
|
||
редактор и настройка первоначально запускаются через `EXEC` как отдельные
|
||
`.EXE`. Это уменьшает HOME, число банков и последствия ошибки компонента.
|
||
|
||
После 1.0 они могут быть интегрированы банками, если измерения покажут
|
||
реальную пользу.
|
||
|
||
## 3. Слои системы
|
||
|
||
```text
|
||
+------------------------------------------------------+
|
||
| app: main loop, mode, active panel, command dispatch |
|
||
+----------------------+-------------------------------+
|
||
| panels/model | UI/dialogs/status |
|
||
+----------------------+-------------------------------+
|
||
| sources | renderer/screen buffer |
|
||
+----------------------+-------------------------------+
|
||
| jobs: copy/... | storage: EMM/page access |
|
||
+----------------------+-------------------------------+
|
||
| platform: ESTEX, keyboard, exec, screen, filesystem |
|
||
+------------------------------------------------------+
|
||
| libc + Sprinter DSS/BIOS |
|
||
+------------------------------------------------------+
|
||
```
|
||
|
||
Зависимости направлены вниз. `platform` не знает о панелях; `storage` не
|
||
показывает сообщения; renderer не читает файловую систему.
|
||
|
||
## 4. Предлагаемая структура исходников
|
||
|
||
```text
|
||
Makefile
|
||
src/
|
||
sprcmd.c точка входа и аварийная очистка
|
||
sc_app.c главный цикл и состояние приложения
|
||
sc_command.c преобразование событий в команды и dispatch
|
||
sc_input.c клавиатурные события
|
||
sc_screen.c EMM framebuffer и dirty rows
|
||
sc_video_system.c координатный вывод ESTEX/BIOS без оконных ID
|
||
sc_draw.c рамки, строки, числа, сокращение путей
|
||
sc_panel.c состояние и поведение панели
|
||
sc_panel_draw.c представление панели
|
||
sc_dir_store.c записи каталога в EMM
|
||
sc_sort.c сравнение и сортировка записей
|
||
sc_source.c диспетчер источников
|
||
sc_source_fs.c настоящий каталог DSS
|
||
sc_copy_job.c PoC-копирование одного файла
|
||
sc_copy_ui.c банковая оркестрация F5 и прогресс
|
||
sc_dialog.c сообщения и подтверждения
|
||
sc_path.c безопасная работа с путями 8.3
|
||
sc_error.c нормализация errno/ESTEX ошибок
|
||
sc_platform_exec.c ESTEX EXEC/WAIT
|
||
sc_platform_screen.c wrappers WINREST/WRCHAR/SCROLL и screen mode
|
||
include/
|
||
sc_app.h
|
||
sc_command.h
|
||
sc_glyphs.h raw-коды CP866 для рамок и стрелок
|
||
sc_input.h
|
||
sc_screen.h
|
||
sc_panel.h
|
||
sc_store.h
|
||
sc_source.h
|
||
sc_job.h
|
||
sc_path.h
|
||
sc_platform.h
|
||
banks/
|
||
bank1_panel.c чтение/сортировка/обновление панелей
|
||
bank2_fileops.c копирование и диалоги операции
|
||
tests/
|
||
... target-тесты и данные для MAME
|
||
docs/
|
||
...
|
||
```
|
||
|
||
Граница файлов может уточняться по карте линковки. Нельзя объединять всё в
|
||
один большой модуль: SDCC/линкер должны иметь возможность не тянуть лишний
|
||
код и разнести холодные функции по банкам.
|
||
|
||
## 5. Состояние приложения
|
||
|
||
### 5.1. `ScApp`
|
||
|
||
В W2 хранится единственный объект состояния:
|
||
|
||
```c
|
||
typedef struct {
|
||
ScPanel panels[2];
|
||
uint8_t active_panel;
|
||
uint8_t running;
|
||
uint8_t video_mode_saved;
|
||
uint8_t original_video_mode;
|
||
uint8_t emm_block;
|
||
uint8_t panel_page[2];
|
||
uint8_t screen_page;
|
||
ScJob job;
|
||
int16_t last_error;
|
||
} ScApp;
|
||
```
|
||
|
||
Фактическое определение может отличаться, но следующие инварианты обязательны:
|
||
|
||
- `active_panel` равен 0 или 1;
|
||
- обе панели ссылаются на разные EMM-страницы;
|
||
- `job` либо неактивен, либо является единственным владельцем своих fd;
|
||
- номера блоков и страниц обнуляются сразу после освобождения;
|
||
- cleanup можно вызвать после любой частично завершённой стадии init.
|
||
|
||
### 5.2. `ScPanel`
|
||
|
||
```c
|
||
typedef struct {
|
||
char path[256];
|
||
uint16_t count;
|
||
uint16_t cursor;
|
||
uint16_t top;
|
||
uint32_t total_size;
|
||
uint8_t source_kind;
|
||
uint8_t phys_page;
|
||
uint8_t truncated;
|
||
uint8_t dirty;
|
||
} ScPanel;
|
||
```
|
||
|
||
Ограничения:
|
||
|
||
- `cursor < count`, если `count != 0`;
|
||
- `cursor == 0`, если `count == 0`;
|
||
- `top <= cursor`;
|
||
- видимый курсор лежит в диапазоне `top..top+26`;
|
||
- `count <= 640` для всей линейки 0.1–1.0;
|
||
- `path` всегда завершён нулём.
|
||
|
||
## 6. Хранилище каталогов
|
||
|
||
Публичный интерфейс не возвращает указатель в W3:
|
||
|
||
```c
|
||
int sc_store_get(const ScPanel *panel, uint16_t index, ScDirEntry *out);
|
||
int sc_store_put(ScPanel *panel, uint16_t index, const ScDirEntry *entry);
|
||
void sc_store_clear(ScPanel *panel);
|
||
```
|
||
|
||
Для пакетных операций допускается закрытая функция, которая отображает
|
||
страницу на короткое время. Она должна:
|
||
|
||
1. Сохранить текущую страницу W3.
|
||
2. Подключить страницу панели.
|
||
3. Выполнить только вычисления над памятью.
|
||
4. Не вызывать ESTEX, renderer, банковский dispatch или пользовательский
|
||
callback.
|
||
5. Восстановить W3 перед возвратом.
|
||
|
||
Сортировка PoC должна быть нерекурсивной и не использовать callback `qsort`,
|
||
чтобы избежать лишнего кода и банковских переходов. Предпочтительный первый
|
||
вариант — Shell sort над одной отображённой страницей.
|
||
|
||
## 7. Чтение настоящего каталога
|
||
|
||
Последовательность `sc_source_fs_scan()`:
|
||
|
||
1. Проверить и нормализовать путь.
|
||
2. Сохранить имя записи под курсором, если панель уже заполнена.
|
||
3. Очистить счётчики панели, но не освобождать её страницу.
|
||
4. При необходимости добавить синтетическую запись `..`.
|
||
5. Выполнить `ffirst("*.*")/fnext` с одним общим 256-байтовым near-буфером.
|
||
Шаблон `*` запрещён: P1 и P3 подтвердили, что DSS считает его именем без
|
||
расширения и не возвращает обычные `NAME.EXT`.
|
||
6. Пропустить `.` и `..` от DSS.
|
||
7. Преобразовать `ffblk_t` в `ScDirEntry` и последовательно записать в EMM.
|
||
8. При достижении 640 записей установить `truncated` и немедленно закончить
|
||
scan: следующий `F_FIRST` сам переинициализирует глобальный итератор DSS.
|
||
9. На стабильном DSS 1.71 код 35 означает, что закончилась единственная
|
||
16-КБ страница кэша каталога, а не весь каталог. Принять уже прочитанные
|
||
записи, установить `truncated` и добавить последней красную виртуальную
|
||
запись `>>> MORE...` с флагом `SC_ENTRY_DSS_LIMIT`. Она занимает один
|
||
слот, не является файлом и не участвует в файловых операциях.
|
||
10. Отсортировать записи: `..`, каталоги, файлы, маркер ограничения; внутри
|
||
обычных групп применить ключ и направление конкретной панели.
|
||
11. Найти прежнее имя и восстановить курсор.
|
||
12. Пометить панель и её экранные строки грязными.
|
||
|
||
Переборы двух панелей никогда не активны одновременно. `ffblk_t` не хранится
|
||
в EMM и не является частью состояния панели. Причина и варианты снятия
|
||
предела DSS разобраны в [dss-large-directories.md](dss-large-directories.md).
|
||
|
||
Флаг `SC_ENTRY_SELECTED` хранится в самой 24-байтовой EMM-записи и переезжает
|
||
вместе с ней при сортировке. Резидентная `ScPanel` содержит только счётчик и
|
||
32-битную сумму выбранных обычных файлов. `..` и виртуальные маркеры никогда
|
||
не выбираются; refresh в текущем срезе 0.2 явно сбрасывает выбор и статистику.
|
||
Одиночный `Insert` может менять флаг каталога, но все групповые операции
|
||
`+`, `-`, `*` сначала отбрасывают `FA_DIREC` и никогда не меняют ранее
|
||
установленный на каталоге флаг.
|
||
|
||
Маски `+`/`-` используют ASCII case-insensitive matcher с `*` и `?` только
|
||
для файлов; специальный DOS-шаблон `*.*` совпадает также с именами файлов
|
||
без точки. Редактор
|
||
маски является общей координатной модальной поверхностью в BANK2: он рисует
|
||
поле в экранной EMM-модели, поддерживает insertion, Backspace, Delete,
|
||
Left/Right/Home/End и не создаёт BIOS-окно.
|
||
|
||
F6/F7 используют тот же editor и один validator компонента DOS 8.3. Локальный
|
||
F6 не передаёт полные пути в DSS `RENAME`: отдельный банковый helper сохраняет
|
||
CWD в резидентный scratch, входит в каталог панели, проверяет отсутствие
|
||
target, вызывает `rename(old_basename, new_basename)` и восстанавливает CWD.
|
||
Перезапись существующей записи запрещена до системного вызова.
|
||
|
||
F8 без выбора удаляет один объект после подтверждения: `unlink` для файла и
|
||
`rmdir` для пустого каталога. Непустой каталог и виртуальная запись не
|
||
запускают разрушительную операцию. При наличии выбора F8 одним
|
||
подтверждением сначала удаляет выбранные файлы верхнего уровня, затем
|
||
обходит все выбранные directory roots общей BFS-очередью, удаляет найденные
|
||
файлы и выполняет `rmdir` каталогов обратным проходом. При ошибке или Esc
|
||
уже выполненные действия не откатываются; CWD и EMM-страница освобождаются.
|
||
|
||
Групповой F5 проходит EMM-store по индексам и передаёт `sc_copy_begin/step`
|
||
только записи с одновременно установленным `SC_ENTRY_SELECTED` и снятым
|
||
`FA_DIREC`. Между файлами долговременный указатель W3 не сохраняется. При
|
||
ошибке уже committed-файлы остаются, активный temp удаляется прежней job;
|
||
это первая зафиксированная политика частично успешной группы.
|
||
|
||
F5 одного текущего каталога использует `sc_tree`: на время операции он
|
||
выделяет отдельную EMM-страницу с BFS-очередью до 1008 каталогов. Узел хранит
|
||
только parent-index, компонент DOS 8.3 и depth; путь восстанавливается перед
|
||
действием. Первый 256-байтовый диапазон страницы хранит исходный CWD.
|
||
Глубина ограничена 32, а путь — 255 символами. Файлы по-прежнему проходят
|
||
через единственную 4-КБ copy-page. Existing target не merge-ится; target
|
||
внутри source запрещается до создания root. Групповой F5 добавляет все
|
||
выбранные каталоги верхнего уровня как независимые roots той же очереди
|
||
после завершения выбранных top-level файлов.
|
||
|
||
## 8. Renderer
|
||
|
||
### 8.1. Интерфейс
|
||
|
||
Минимальные операции:
|
||
|
||
```c
|
||
void sc_screen_clear(uint8_t attr);
|
||
void sc_screen_cell(uint8_t x, uint8_t y, uint8_t ch, uint8_t attr);
|
||
void sc_screen_text(uint8_t x, uint8_t y, const char *s, uint8_t attr,
|
||
uint8_t max_width);
|
||
void sc_screen_fill(uint8_t x, uint8_t y, uint8_t width,
|
||
uint8_t ch, uint8_t attr);
|
||
int sc_screen_scroll_rows(uint8_t x, uint8_t y, uint8_t width,
|
||
uint8_t height, int8_t delta);
|
||
void sc_screen_mark_rows(uint8_t first, uint8_t count);
|
||
int sc_screen_flush(void);
|
||
```
|
||
|
||
Экранный буфер — источник истины. Диалог рисуется в тот же буфер поверх
|
||
панелей. Для PoC допустима перерисовка панелей после закрытия диалога вместо
|
||
отдельного стека сохранённых окон.
|
||
|
||
### 8.2. Обновление
|
||
|
||
- Dirty mask занимает 32 бита в W2.
|
||
- Изменение курсора помечает старую и новую строку.
|
||
- Прокрутка панели помечает строки 1–27 её половины экрана.
|
||
- При изменении `top` ровно на одну строку `sc_screen_scroll_rows()` сдвигает
|
||
одновременно EMM-модель и экранный прямоугольник, затем renderer строит
|
||
только открытую строку. PgUp/PgDn и большой скачок делают полный redraw
|
||
области панели.
|
||
- Смена каталога перерисовывает соответствующую половину и общие строки
|
||
29–31.
|
||
- Полный `sc_video_present_full()` используется после запуска, возврата из
|
||
`.EXE` и крупных изменений.
|
||
- `sc_video_present_rect()` группирует соседние грязные строки.
|
||
|
||
App-local WINREST wrapper проверен P1-пробой Commander: полный экран 80x32 и
|
||
окно 20x2 из offset `0x2000` восстановились корректно. P2.1 затем подтвердил
|
||
полную сверку через `RDCHAR`, оба направления прямоугольного scroll и 1000
|
||
операций. Координатный backend принят как основной пакетный способ вывода;
|
||
ограничения и артефакты описаны в
|
||
[результатах P2.1](p2-screen-probe-results.md).
|
||
|
||
## 9. Ввод и команды
|
||
|
||
`sc_input.c` преобразует `getkey()` и `kbd_mod_state()` в `ScKeyEvent`:
|
||
|
||
```c
|
||
typedef struct {
|
||
uint8_t ascii;
|
||
uint8_t scan;
|
||
uint8_t modifiers;
|
||
} ScKeyEvent;
|
||
```
|
||
|
||
Модификаторы хранят только Shift, Ctrl и Alt; состояния CapsLock, NumLock и
|
||
Rus/Lat не участвуют в выборе команды.
|
||
|
||
P1 установил особенность DSS: при модифицированной клавише бит 7 raw
|
||
scan-кода установлен. Например, Ctrl+R даёт `93`, Ctrl+PgUp — `D9`, Alt+F1
|
||
— `BB`. `sc_input` нормализует их в `13`, `59`, `3B` через `scan & 0x7F`,
|
||
но только после чтения compact modifiers. Таблица команд никогда не содержит
|
||
raw-коды с битом 7.
|
||
|
||
`sc_command.c` превращает событие в `ScCommand`. Остальные модули не должны
|
||
сравнивать scan-коды непосредственно. Команды описаны в
|
||
[commander-keymap.md](commander-keymap.md).
|
||
|
||
В MAME `kbd_mod_state()` в момент возврата `getkey()` сохранил активный
|
||
модификатор: compact Ctrl=`02`, Alt=`04`, Shift=`01`. Для переносимости
|
||
управляющие ASCII-коды (`Ctrl+R` и подобные) всё равно имеют явные алиасы,
|
||
не зависящие от live-состояния.
|
||
|
||
## 10. Копирование как конечный автомат
|
||
|
||
Состояния PoC:
|
||
|
||
```text
|
||
IDLE
|
||
-> VALIDATE
|
||
-> CREATE_TEMP
|
||
-> OPEN_SOURCE
|
||
-> COPY_CHUNK <----+
|
||
-> CLOSE_FILES |
|
||
-> RENAME_TEMP |
|
||
-> REFRESH |
|
||
-> DONE |
|
||
|
|
||
COPY_CHUNK -----------+ пока done < total
|
||
|-> CANCEL -> CLEANUP -> DONE
|
||
+-> ERROR -> CLEANUP -> DONE
|
||
```
|
||
|
||
Правила:
|
||
|
||
- конечное имя проверяется до создания temp;
|
||
- temp создаётся в целевом каталоге с `O_CREAT|O_EXCL`;
|
||
- имя temp имеет форму `~SCxxxx.TMP` и перебирается с ограниченным числом
|
||
попыток;
|
||
- рабочая область находится в отдельной EMM-странице и временно отображается
|
||
в W3; 4-КБ массива в W2 и изменяемого буфера в банке кода нет;
|
||
- начальный логический размер блока — 4096 байт, чтобы между блоками часто
|
||
обновлять прогресс и проверять отмену; после измерений он может быть увеличен
|
||
вплоть до размера страницы;
|
||
- чтение выполняется существующим `bank_read_page()` прямо из открытого fd в
|
||
страницу; для записи нужен симметричный `bank_write_page()`, который сохраняет
|
||
и восстанавливает отображение W3;
|
||
- открыто не более двух fd;
|
||
- успешность `write()` определяется его библиотечным контрактом, а не
|
||
сырым DE ESTEX;
|
||
- при short write оставшаяся часть блока дописывается или операция завершается
|
||
ошибкой;
|
||
- после успешного закрытия temp конечное имя проверяется повторно, и только
|
||
затем temp переименовывается в него;
|
||
- FAT date/time задаются temp через `PUT_D_T` перед close; после rename
|
||
конечное имя получает исходные R/H/S/A через `ATTRIB`;
|
||
- ошибка после rename помечается как ошибка финализации уже committed-файла,
|
||
а не как полное отсутствие результата;
|
||
- любое завершение проходит через единый cleanup;
|
||
- после успеха перечитывается пассивная панель.
|
||
|
||
P19 добавляет policy существующего файла. Safe overwrite сначала переводит
|
||
старый target в уникальный `~SBxxxx.BAK`, затем commit-ит temp. При отказе
|
||
второго rename backup возвращается на место вместе с исходными атрибутами;
|
||
после успешных метаданных нового target backup удаляется. `overwrite all` и
|
||
`skip all` — локальное состояние одного F5. Readonly не наследует молча
|
||
`overwrite all`, а повторно показывает choice-dialog.
|
||
|
||
Для версии 0.2 этот автомат расширяется очередью файлов, обходом каталогов,
|
||
политиками overwrite/skip/rename и сохранением метаданных.
|
||
|
||
## 11. Запуск дочерней программы
|
||
|
||
Последовательность:
|
||
|
||
1. Завершить или запретить активный job.
|
||
2. Сформировать проверенный абсолютный путь `.EXE`.
|
||
3. Запомнить активную панель, курсоры и ожидаемый CWD; временно сделать путь
|
||
активной панели текущим каталогом child.
|
||
4. Выполнить синхронный ESTEX `EXEC` с `B=1` и сразу сохранить его результат
|
||
до других вызовов libc.
|
||
5. Восстановить ожидаемые диск и CWD.
|
||
6. Повторно установить текстовый режим 80x32 и палитру Commander, не меняя
|
||
сохранённую палитру для финального shutdown.
|
||
7. Перечитать обе панели: внешняя программа потенциально могла изменить оба
|
||
их каталога.
|
||
8. Полностью вывести экран из screen page.
|
||
9. При ошибке показать её штатной строкой состояния Commander.
|
||
|
||
P1 подтвердил контракт wrapper: C-сигнатура
|
||
`p1_exec(uint8_t path_mode, const char *path)` получает mode в A и path в DE,
|
||
затем перекладывает их в B и HL для ESTEX. `B=0` с коротким именем и `B=1`
|
||
с явным относительным путём работают; probe получил `0x5A`. В Commander
|
||
низкоуровневый `sc_platform_exec(path)` фиксирует `B=1`, принимает путь в HL
|
||
по `__sdcccall(1)` и возвращает код завершения как `int` в DE.
|
||
|
||
### 11.1. Командная строка и `Ctrl+O` в 1.0
|
||
|
||
В VC 4.05/4.99 `Ctrl+O` переключает панели и user screen, а редактор `DosBuf`
|
||
работает вместе с prompt и историей. Dos Navigator дополнительно показывает
|
||
самостоятельный `TCommandLine` с горизонтальной прокруткой и историей. На Sprinter
|
||
родительская DSS-shell приостановлена, поэтому её нельзя превратить в интерактивный
|
||
фон простым скрытием панелей.
|
||
|
||
Поэтому 1.0 использует собственный pseudo-shell:
|
||
|
||
- однострочный prompt на экране панелей и полноэкранный режим делят один
|
||
редактор, историю и модель текущего каталога;
|
||
- `Ctrl+O` переключает режимы; возврат к панелям полностью строит их из
|
||
`ScPanel`, поэтому отдельная копия экрана панелей не нужна;
|
||
- builtin-команды минимум `cd`, смена диска, `dir`, `cls`, `pwd`, `help`;
|
||
- внешняя команда запускается через подтверждённый ESTEX `EXEC`; после возврата
|
||
pseudo-shell восстанавливает видеорежим, CWD и свою экранную модель;
|
||
- вывод и скролл идут через тот же безоконный screen backend; прямой VRAM и BIOS
|
||
window manager не используются;
|
||
- полноэкранные ячейки могут повторно использовать текущую screen page. До
|
||
фиксации поведения внешнего `.EXE` отдельной пробой проверяется `WINCOPY`.
|
||
|
||
## 12. Ошибки и владение ресурсами
|
||
|
||
Низкий уровень возвращает `0/-1` и устанавливает `errno`. UI не анализирует
|
||
Carry Flag или регистры DSS.
|
||
|
||
Каждый ресурс имеет одного владельца:
|
||
|
||
| Ресурс | Владелец |
|
||
|---|---|
|
||
| общий EMM-блок PoC | `ScApp` |
|
||
| физическая страница панели | `ScPanel`, без права освобождения блока |
|
||
| экранная страница | `sc_screen`, без права освобождения блока |
|
||
| рабочая страница копирования | `ScApp`/copy job, без права освобождения блока |
|
||
| fd источника и temp | активный `ScCopyJob` |
|
||
| исходный видеорежим | `ScApp` |
|
||
| временно отображённая W3 | текущая функция storage/screen |
|
||
|
||
Cleanup выполняется в обратном порядке инициализации. Нельзя прятать
|
||
владение fd или EMM-блоком в локальном static-состоянии модуля.
|
||
|
||
## 13. Предварительная карта кода
|
||
|
||
HOME/W2 содержит только постоянно необходимое:
|
||
|
||
- crt0, банковские трамплины и используемую libc;
|
||
- init/cleanup;
|
||
- главный цикл;
|
||
- ввод и базовый dispatch;
|
||
- EMM accessors;
|
||
- screen flush;
|
||
- критическую обработку ошибок.
|
||
|
||
Предварительные банки:
|
||
|
||
| Банк | PoC | Полная версия |
|
||
|---:|---|---|
|
||
| 1 | panel scan, sort, redraw model | панели, сортировки, режимы панели |
|
||
| 2 | copy job, operation dialog | копирование/перемещение/удаление |
|
||
| 3 | не нужен | меню, конфигурация, история |
|
||
| 4 | не нужен | поиск и дерево |
|
||
| 5 | не нужен | архивные/виртуальные источники |
|
||
|
||
Номер банка является деталью сборки, а не публичным ABI. Банковские модули
|
||
не должны хранить долгоживущие payload-данные в локальном сегменте банка.
|
||
Краткоживущий `ffblk_t` является обычным near-буфером; закреплять его в хвосте
|
||
W1 бессмысленно, поскольку граница больших каталогов находится внутри DSS.
|
||
|
||
После каждой сборки проверяются:
|
||
|
||
- размер HOME вместе с DATA/BSS и heap;
|
||
- запас каждого банка до 16 КБ;
|
||
- отсутствие прямых вызовов в чужой банк;
|
||
- расположение трамплинов;
|
||
- стек W2 при всех вызовах ESTEX.
|
||
|
||
## 14. Эволюция после PoC
|
||
|
||
### Версия 0.2
|
||
|
||
- одностраничное хранилище остаётся ограничено 640 записями;
|
||
- выбранные файлы и маски;
|
||
- очередь операций;
|
||
- рекурсивный обход;
|
||
- local rename, mkdir, recursive group copy/delete выполнены; group move и
|
||
защитная policy остаются;
|
||
- транзакционная перезапись файлов и conflict-policy выполнены; directory
|
||
merge и delete/move policy остаются;
|
||
- атрибуты и время обычных файлов выполнены; метаданные каталогов — позже.
|
||
|
||
### Версия 0.4
|
||
|
||
- командная строка, история и полноэкранный pseudo-shell по `Ctrl+O`;
|
||
- associations/user menu;
|
||
- внешний viewer/editor/setup;
|
||
- конфигурационный файл;
|
||
- переключение дисков.
|
||
|
||
### Версия 0.6
|
||
|
||
- quick view и info source;
|
||
- дерево и поиск;
|
||
- фильтры и сравнение каталогов;
|
||
- подсчёт размеров каталогов;
|
||
- описания файлов.
|
||
|
||
### Версия 0.8+
|
||
|
||
- `SC_SOURCE_SEARCH` как виртуальная панель;
|
||
- `SC_SOURCE_ARCHIVE`;
|
||
- протокол внешних helper-программ;
|
||
- мышь, помощь, локализация;
|
||
- измеренная оптимизация кэшей и renderer.
|
||
|
||
### Версия 2.0+
|
||
|
||
- динамическое выделение EMM-страниц панели;
|
||
- предел 1024/2048 или отсутствие продуктового предела после измерений;
|
||
- межстраничная сортировка без полной копии каталога в W2;
|
||
- деградация при нехватке EMM и тесты частично выделенных цепочек страниц.
|
||
|
||
## 15. Архитектурные запреты
|
||
|
||
- Нельзя вызывать DSS при стеке вне W2.
|
||
- Нельзя держать одновременно восемь файловых дескрипторов.
|
||
- Нельзя сохранять W3 pointer в состоянии приложения.
|
||
- Нельзя менять W1 вручную из банковской функции.
|
||
- Нельзя вызывать чужой код при оставленной временной странице W3.
|
||
- Нельзя выполнять разрушительную операцию без известного cleanup-path.
|
||
- Нельзя смешивать вывод через `printf` с экранной моделью во время работы UI.
|
||
- Нельзя добавлять функцию DN только потому, что она присутствует в DN;
|
||
каждая функция должна соответствовать roadmap Commander.
|