Metadata-Version: 2.1
Name: pywjs
Version: 0.0.5
Summary: 
Home-page: https://github.com/denisxab/py_wjs
Author: Denis Vetkin
Requires-Python: >=3.11,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Requires-Dist: aiosqlite (>=0.17.0,<0.18.0)
Requires-Dist: jsonpickle (==2.2.0)
Requires-Dist: logsmal (==0.0.14)
Requires-Dist: pydantic (>=1.10.2,<2.0.0)
Requires-Dist: websockets (>=10.4,<11.0)
Project-URL: Repository, https://github.com/denisxab/py_wjs
Description-Content-Type: text/markdown

---
date created: 2022-11-22 23:15
date updated: 2022-11-22 23:20
---

# Введение

Причина появление этой штуковины обосновано тем, что в `Python`, **по моему мнению**, нет достойных фреймворков для создания **коммерческих** десктоп приложений. `Qt` имеет лицензию `GPL`, а это значит что создавать коммерческие приложения на нем не законно, `Tkinter` стар, `Kivy` хорош, и лицензия `MIT`, но вам придется изучать `Kv language`, чтобы в нем разобраться, а найти товарища, который знает, или хочет изучать `Kivy`, будет тяжело, поэтому HR-ам будет туго. А вот `HTML`,`CSS`,`JS` сейчас преподают в начальных классах школы, поэтому товарищей по разработки, и готовой технологий много.

Мне нужен был фреймворк:

1. В котором можно использовать все библиотеки, и достижения в `Python`, и полный доступ к операционной системе(поэтому `webassembly` не подходит).
2. Возможность использовать все библиотеки, и достижения WEB фронтенда.

Мы(я) решили что `Python` хорош и удобен - во всем, но заточен для бекенде, а `JavaScript` приходится использовать на фронтенде, поэтому мы(я) сделали удобную интеграцию, под слоганом **"`Python` в `JavaScript`"**.

> Я бы хотел чтобы `Python` активно использовали для создания коммерческих десктоп приложения, чтобы было больше таких вакансий, чтобы `Python` программистам которые хотят создавать десктоп приложения, зарабатывали на любимом деле, и не мучились бы со всякими `Delphi`/`Java` потому что только такие, комические вакансии с **низким уровнем входа**, есть на рынке труда. Ричард Мэттью Столлман молодец, что агитирует за свободное ПО(не коммерческое), но быть доставщиком пиццы (на одном из интервью, он так советует программистам зарабатывать на жизнь) у мня плохо получается. Я не имею нечего против свободного ПО, я за свободное ПО, но когда свободные ПО запрещают(или делают платным) для использовать в коммерческих целях, это не свободное ПО -> это проприетарное ПО. Какая вредность от того, что твое ПО используют в коммерческих целях, что кто то обогащаются с помощью него ? В конечном итоге, это же помогает развитию бизнеса, что в следствие помогает развитию общества. Если свободное ПО нельзя использовать в коммерческих целях, то тогда будут использовать проприетарное ПО в коммерческих целях... как говориться в "Притче о двух волка"- "Всегда побеждает тот волк, которого ты кормишь". **Поэтому `Pywjs` свободен(бесплатен) для коммерческого(и не коммерческого) использования**, у него даже нет лицензии и авторского права, поэтому если кото захочет украсть проект - я будут рад форку :)

`Python` Выступает в качестве сервера, он по умолчанию асинхронный, и может одновременно обрабатывать десятки соединений, он может быть запущен как локально, так и удаленно. `JavaScript` отвечает за клиентский код, за визуал, вы можете использовать все доступные WEB инструменты для написания фронтенда, например сейчас есть поддержка `Vue.js`([Как пользоваться в Vue.js](#как-пользоваться-во-vuejs)).

---

- Список фич:
  - Для быстрой и надежной интеграции, используются сетевой протокол `WebSocket`, и готовые стандартизированные `JSON` схемы([JSON Схема](#json-схемы)), модифицированный `json-rpc`.
  - Со времени форматирования сообщения до момента получения результат от сервера, отклик - в среднем `0.002` секунды.
  - По умолчанию безопасность сервера обеспечивается обязательной аутентификацией по токену. Это имеет смысл с использованием шифрования `TLS`, но и без него это помогает избежать лишних подключений, например если у вас несколько приложений на `pywjs` и клиент случайно пытается подключиться не тому приложению.
  - Автоматическое переподключение к серверу, при потере соединения с ним(на стороне `JavaSctipt`). Это невероятно полезная вещь при разработке программы на стороне сервера, когда его приходится постоянно перезапускать.
  - Для удобного и надежного создания десктоп приложений, есть несколько готовых вариантов отправки сообщений по протоколу `WebSocket`([Варианты отправки сообщения](#варианты-отправки-сообщения)), например вы есть вариант `send_dependent`, в нем, ваше сообщение будет как, обязательная зависимость для `Python`. Если потеряется связь с сервером, то при пере подключение к нему, ваши команды автоматически отправятся снова, это идеально подходит для динамического импорта([import_from_server](#import_from_server)) модулей на `Python` сервер . Или например транзакции `send_transaction`([Описание работы транзакций](#описание-работы-транзакций)), в которых вы можете гарантированно отправлять сообщения, и получать на них ответ, а иначе, при возникновении множество возможных ошибок(например в течение 5 секунд, от сервера не пришел результат), обрабатывать их.

---

Связанные проекты с `pywjs`:

- [-> Корневой проект `py_wjs` и итеграция для `Python`](https://github.com/denisxab/py_wjs)
- [-> Клиентский проект `pyw_js` и интеграция для `JavaScript`](https://github.com/denisxab/pyw_js)
- [-> Тестовый стенд / Демо версия](#тестовый-стенд)
- Примеры проектов на `pywj`:
  - [Файловый проводник](https://github.com/denisxab/pywjs_explorer)

---

Принципы `pywjs` программы:

- Все необходимое должно быть их коробки, и поддерживаться официальным автором. Плагины это хорошо, но зоопарк плагинов это плохо.
- У пользователей не должно быть возможности сломать программу через стандартный функционал. А если он сломал программу через НЕ стандартный функционал, то попробовать восстановить целостность программы.
- Автономность программы - у каждого проекта свое виртуально окружение, и свой механизм обновления, это защищает от несовместимостей версий, которые могут возникнуть в глобальных ВО и механизмов обновления.
- Возможность использования сервера, как для десктопнго приложение, так и сетевого. С минимальными изменениями, в пользовательском коде фреймворка.  

# Интеграция

## JavaScript

### Быстрый старт JavaScript

Интеграция на стороне `JavaScript` основана на протоколе `WebSocket`. Браузер - клиент, Операционная система с `Python` - сервер.

#### Как пользоваться в JavaScript

1. Для интеграции нужно импортировать в `HTML` файлы - `wbs.js`(для логики) и `wbs_type.js`(для типов)

   ```html
   <script src="/js/wbs_type.js" defer></script>
   <script src="/js/wbs.js" defer></script>
   ```

2. Подробный пример шаблона для интеграции клиента на чистом `JavaScript`. Для безопасности сервера, подключения к нему осуществятся через токен. Вы можете придумать любой токен и вставить его вместо `ЛюбойТокенКоторыйВыРазрешилиНаСервере`.

   ```ts
   const wbs_obj = new Wbs("ЛюбойТокенКоторыйВыРазрешилиНаСервере", {
        // Хост
        host: "localhost",
        // Порт
        port: "9999",
        // Функция для отправки сообщений на сервер
        callback_onopen: () => {
            /*
            В этом примере мы отправляем запрос на сервер чтобы он посчитал 2+2
            */
            const command = "2+2";
            wbs_obj.send({
                mod: ClientsWbsRequest_Mod.exe,
                h_id: 99,
                uid_c: 0,
                body: {
                    exec: command,
                },
            });
        },
        // Функция для получения сообщений от сервера
        callback_onmessage: (event: MessageEvent) => {
            /*
            Здесь мы получем все ответы от сервера.

            Чтобы можно было по разному обрабатывать ответы от сервера 
            есть атрибут `h_id`, которые мы передаем в запрос, и на который получаем здесь ответ.
            */
            const response_obj = <ServerWbsResponse>JSON.parse(event.data);
            switch (response_obj.h_id) {
                case 99:
                    {
                        console.log(response_obj.response);
                    }
                    break;
            }
            /* wbs_obj.close(WbsCloseStatus.normal,'Пример Закрытия Соединения') */
        },
        // Функция обработка закрытия соединения с сервером
        callback_onclose: undefined,
        // Функция обработок ошибок при отправке на сервер
        callback_onerror: undefined,
        // Событие = Успешное подключение к серверу
        event_connect: undefined,
        // Событие = Не удалось подключиться к серверу
        event_error_connect: undefined,
        // Имя пользователя для этого клиента, используется в "кеш пользователя", по умолчанию user='base'
        user: "ИмяПользователя",
   });
   ```

### Быстрый старт Vue.js

Итерация `Pywjs` во `vue.js` происходит через хранилище(`vuex`). Всё взаимодействие с `WebSocket` происходит в хранилище `wbsStore.ts`

#### Как пользоваться во Vue.js

1. Подключаем `wbsStore.ts` к проекту

   1. Подключаем хранилище как модуль: `/src/store/index.ts`

      ```ts
      import { createStore } from "vuex";
      import { wbsStore } from "./wbsStore";

      export default createStore({
        modules: { wbs: wbsStore },
      });
      ```

   2. В компоненте `/src/App.vue` в методе `mounted` инициализируем подключение к `WebSocket`. После этого можно отправлять сообщения.

      ```ts
      beforeCreate() {
        this.$store.dispatch("wbs/initWebSocket", {
            // Что сделать после успешного подключения к серверу.
            after_connect: () => {
                // Тут отправляем первые сообщения на сервер.
            },
            // Обработка события window.beforeunload(закрытия/перезагрузка страницы). Здесь можно выполнять отчистку ресурсов.
            destruction:()=>{}
        });
      },
      ```

2. Отправляем сообщение на сервер.

   - Отправка сообщение из другого хранилища:

     ```ts
     actions: {
        ЛюбоеИмя({dispatch}){
            dispatch(
                "wbs/send",
                {
                    // ПРИМЕР
                    mod: ClientsWbsRequest_Mod.exec,
                    h_id: 1,
                    body: {
                        exec = "2+2",
                    },
                },
                { root: true }
            );
        }
     }
     ```

   - Отправка сообщения из компонента:

     ```ts
     methods: {
        ЛюбоеИмя(){
            this.$store.dispatch("wbs/send", {
                // ПРИМЕР
                mod: ClientsWbsRequest_Mod.exec,
                h_id: 1,
                body: {
                    exec = "2+2",
                },
            });
        }
     }
     ```

3. Получить ответ от сервера. В хранилище `wbsStore.ts` все ответы хранятся в `state.res.value`. Ключи у `state.res.value` будут равны той цифре которую вы указали в запросе в параметре `h_id`. В примере выше мы указывали `h_id: 1`, поэтому получим ответ от `value[1]`.

   - Получить ответ в другом хранилище, для реактивности используем `getters`:

     ```ts
     getters: {
        ЛюбоеИмя(rootState) {
            const r=rootState.wbs.res.value[1]
            return r ? r : {};
        }
     }
     ```

   - Получить ответ в компоненте, для реактивности используем `computed`:

     ```ts
     computed: {
        ЛюбоеИмя() {
            const r=this.$store.state.wbs.res.value[1]
            return r ? r : {};
        }
     }
     ```

#### Использование алиасов для h_id

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

1. Создать алиасы:

   Например это файл `./store/Хранилище`

   ```ts
   import { ClassHID } from "wbs/wbs";
   export const Алиасы = new ClassHID({
       Алиас: Число_h_id,
   });
   ```

2. Использование:

   ```ts
   // Получить имя алиаса по числу `h_id`
   Алиасы.ids[Число_h_id];
   // Получить число `h_id` по имени алиаса   
   Алиасы.names.Алиас;
   ```

#### Виджет для мониторинга подключения `pyjs_log.vue`

Готовый виджет для контроля подключения, и просмотра всех ответов от сервера.

![pyjs_log](./img/pyjs_log.png)

1. Показать/Скрыть окно.
2. Статус подключения к серверу.
3. Ответ сервера, на выбранный `h_id`
4. Список `h_id` на которые есть ответы.
5. Введите новый `URL`(для `WebSocket`) которому вам нужно подключиться.
6. Подключиться к новому `URL`(для `WebSocket`).

---

- Подключить `pyjs_log` в `App.vue`

  ```html
  <template>
      <PyjsLog :hids="hids" />
  </template>
  <script lang="ts">
    import PyjsLog from "@/components/pyjs_log.vue";
    import { Алиасы } from "./store/Хранилище";
    export default {
        components: { PyjsLog },
        data() {
            return {
                // Алиасы для h_id. Об этом написаны в главе [-> Использование алиасов для h_id]]
                // Можно не указывать, тогда вместо алиасов будут `h_id.`
                hids: Алиасы,
            };
        },
    };
  </script>
  ```

#### Тестовый стенд

Для того чтобы стразу попробовать программу на практике, воспользуйтесь тестовым стендом. В нём можно протестировать весь функционал `pywjs`.

<https://github.com/denisxab/pywjs_test_stand>

### Взаимодействие с сервером

#### Варианты отправки сообщения

- `wbs_obj.send({Запрос})` - Отправить сообщение на сервер. Это базовый вариант для отправки сообщений, все другие варианты используют этот, но добавляют некоторые особенности. Описание: Нет гарантий того, что вы получите на него ответ, если связь с сервером оборвется. И сообщение не будет ожидать подключения, прежде чем отправиться ! Если подключения нет, то сообщение пропадет, но целостность сообщения и порядок гарантирован. Наверное это слишком демотивирующие описание, того как работает протокол `TCP`.

  ```ts
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.exec,
    h_id: 99,
    // uid_c:  Автоматически сгенерируется
    body: {
    exec = "2+2",
    },
    // t_send: Автоматически сгенерируется
  });
  ```

  - `mod` - [Модификации запросов на сервер](#модификации-запросов-на-сервер).
  - `h_id` - [Кто такой h_id ?](#кто-такой-h_id).
  - `body` - [Каким бывает body ?](#каким-бывает-body).
  - `uid_c` - Логическое разделение сообщений, в пределах одного `h_id`, по умолчанию берется из генератора `Wbs.getUidC()`.
  - `t_send` - Время отправки сообщения(в UNIX). Используется для замера времени выполнения команды. По умолчанию, после получения сообщения от севера, формируется атрибут `t_exec` который и указывает на время выполнения команды (в секундах).

- `wbs_obj.send_force({Запрос},ЗадержкаПереотправки)` - Принудительно отправить сообщение на сервер. Если для указанной `uid_c` в течение `ЗадержкаПереотправки` не будет ответа от сервера, то это сообщение отправиться снова, это будет происходить пока мы не получим ответ для указанной `uid_c`. Сообщение **НЕ** будет, пере отправится, если во время ожидания ответа, связь с сервером была потеряна, он дождется восстановления подключения, и продолжит пере отправлять сообщение. Я называю это - **"наглая транзакция"**, этот вариант отправки использует для `send_dependent`. Вы, можете использовать `send_force`, если любую возможную проблему, можно исправить обычной пере отправкой сообщения. Как говорят люди которые не первый день в программировании, 50% багов решаются обычной перезагрузкой/переотправкой.

- `wbs_obj.send_dependent({Запрос},ЗадержкаПереотправки)`- Отправить команду, которая являются зависимостью для сервера. Если произошел обрыв связи с сервером, то при успешном пере подключение к нему, эти команды будут автоматически переотправлены. Отличие от `send_force` в том - что `send_force`, отправляет единожды сообщение, а `send_dependent` запоминает сообщение, и автоматически отправляет его, при каждом переподключение. Это полезна например для модификации запроса `import_from_server`, при таком методе отправки, вы можем быть уверены, что указные модули, будут обязательно импортированы на сервер, даже если он перезагрузился.

- `wbs_obj.send_transaction({Запрос},ЧтоВыполнитьЕслиОтветНеПолучен)` - Выполнить команду в режиме транзакции. [Описание работы транзакций](Без-названия-3.md#Описание-работы-транзакций). Сейчас транзакции поддерживаются только для модификаций запросов `func`. В `send_transaction` происходит подмена модификации `func` на её транзакционную модификацию `transaction_func`, поэтому в отправляемом `json` будет `mod:101` а не `mod:2`. **Главная причина** почему это нужно использовать - это обработка возможных ошибок, в функции `rollback`. Если в `send_force` все ошибки решаются обычной переотправкой, то в `send_transaction` вы можете осмысленно обрабатывать исключения. **Например** - вам нужно выполнить консольную команду на сервере, и вы бы не хотели, чтобы какая нибудь команда вдруг не выполнилась, и вы бы даже об этом не узнали. Вы бы могли придумать надежный вариант передачи команды через обычный `send`, но я уже это сделал за вас. При использовании `send_transaction` вы можете быть уверены - что команда, будет отправлена, выполнена, и вы получите успешный ответ, а иначе, все возможные ошибки будут переданы в функцию `rollback`, и в ней вы обработаете эти ошибки.

  ```ts
  wbs_obj.send_transaction(
      {
        mod: ClientsWbsRequest_Mod.func,
        h_id: 99,
        body: {
            n_func: "os_exe_async", // Имя функцию которую вызвать
            args: ["ls"], // Позиционные аргументы
            kwargs: undefined, // Именованные аргументы
        },
      },
      // Это функция `rollback`
      (error_code: TRollbackErrorCode, h_id: number, uid_c: number) => {
        alter("Rollback");
      }
  );
  ```

- `wbs_obj.send_before({ПервыйЗапрос,before})` - Последовательная отправка сообщений. Выполнить отправку `ПервогоЗапроса` на сервер, через вариант `send_force`, ожидать на него ответ, после получения успешного ответа, выполняет функцию `before`, в которую передаст ответ `ПервогоЗапроса`. Это идеально подходит для получения "кеша пользователя", и последующего его использования в другом запросе на сервер. [-> **Использование кеша пользователя на стороне клиента**](#использование-кеша-пользователя-на-стороне-клиента)

  ```ts
  /* 
  Условный Пример: Когда пользователь закрывает страницу, мы записываем в пользовательский кеш,последний путь в кортом он был. 
  Когда пользователь вновь откроет страницу, произойдет запрос с вариантом `send_before` для получения из кеша последнего пути. 
  После получения пути, делаем запрос на сервер для получения всех файлов. 

  Таким образом можно сохранять состояние приложения на диске(для душнил=на запоминающем устройстве).
  */
  wbs_obj.send_before({
      // ПЕРВЫЙ ЗАПРОС
      mod: ClientsWbsRequest_Mod.cache_read_key,
      h_id: 87,
      body: {
        // Например: получаем путь к папке, в котрой пользователь был до закрытия страницы.
        key: "ПрошлыйПуть",
      },
      // ВТОРОЙ ЗАПРОС
      before: (last_res: ServerWbsResponse) => {
        // Получаем прошлый путь из кеша
        const last_path = JSON.parse(last_res);
        wbs_obj.send({
            mod: ClientsWbsRequest_Mod.func,
            h_id: 99,
            body: {
                // Например: Получим все файлы в указанной директории
                n_func: "ФункцияДляПолученияФайлов",
                kwargs: { path: last_path.response },
            },
        });
      },
  });
  ```

#### Кто такой h_id

В `Pywjs` используется своеобразный способ получения сообщений. Так как по `WebSoket` у нас одно подключение(потому что это удобно), а ответом нужно получать много, используется вариант логического разделения ответа по `h_id`.

Например, клиент делает запрос в котором указывает `h_id=99`, сервер после обработки этого сообщения, вернет ответ с этим же `h_id=99`. Например, в реализации на `Vue.js` нужно использовать вычисляемые переменные, для реактивного реагирования, на ответ сервера, с конкретным `h_id` [-> Быстрый старт Vue.js](#быстрый-старт-vuejs).

#### Каким бывает body

Атрибут `body` дает универсальность для запросов, которые используют различие [Модификации запросов на сервер](#модификации-запросов-на-сервер). По сути модификации `mod` - это как обработать сообщение, а `body` - это само сообщение.

**Все доступные варианты** `body` смотрите в `ClientsWbsRequest.body`

#### Описание работы транзакций

Как происходит передача сообщения в транзакции:

| № | Клиент                                                                                                 |    | Сервер                                           |
| - | ------------------------------------------------------------------------------------------------------ | -- | ------------------------------------------------ |
| 1 | Отправка сообщения на сервер.                                                                          | -> | Сервер получает сообщение,                       |
| 2 | Клиент ожидает(указанное количество секунд) уведомления от сервера, о том что он принял сообщение.     | <- | и отправляет об этом уведомление клиенту.        |
| 3 | Клиент ожидает(указанное количество секунд) результат от сервера. Только если было принято уведомление | <- | Сервер выполняет команду, и отправляет результат |

Возможные ошибки в транзакции, и их обработка:

- На №1 = Сообщение не отправлено на сервер, тогда сработает `rollback` с кодом `TRollbackErrorCode.timeout_notify`- превышено время ожидания уведомления.
- На №2 = Сервер, по какой-либо причине, не уведомил клиента о получение сообщения, тогда сработает `rollback` с кодом `TRollbackErrorCode.timeout_notify`- превышено время ожидания уведомления.
- На №3 = Сервер уведомил клиента о получение сообщения, но по какой-либо причине не вернул результат команды, тогда сработает `rollback` с кодом `TRollbackErrorCode.timeout_response`- превышено время ожидания результат.
- На №3 = Во время выполнения команды, на сервере произошло не обработанное исключение, и он вызвал на своей стороне `rollback`, тогда сработает клиентский `rollback` с кодом `TRollbackErrorCode.error_server`- откат по причине ошибки выполнения на сервера.

#### Модификации запросов на сервер

Все доступные модификации запросов хранятся в `ClientsWbsRequest_Mod`.

- Простые:

  1. `exec=3` - Выполнить произвольную команду на сервере.
  2. `import_from_server=4` - Динамически импортировать указанные модули на сервер, только для `exec`(Выполнения произвольной команды).
  3. `info=1` - Получить служебную информацию о сервере.

- [-> Доступные функции](#доступные-функции):

  1. `func=2` - Выполнить доступную функцию на сервере. _Чаще всего вы будете использовать эту модификацию запроса_.

- [-> События на сервере](#события-на-сервере)

  1. `event_create=5` - Запустить отслеживание события на сервере, и подписаться на него.
  2. `event_sub=6` - Подписаться на событие сервера.
  3. `event_unsub=7` - Отписаться от события сервера.

- [1-> Кеш пользователей](#кеш-пользователей) [2-> Использование кеша пользователя на стороне клиента](#использование-кеша-пользователя-на-стороне-клиента)
  1. `cache_add_key=8` - Создать(или обновить) ключ, который содержит пользовательский кеш.
  2. `cache_read_key=9` - Получить пользовательский кеш по указному ключу.

---

Рассмотрим каждую модификацию, в следующих главах.

Для того чтобы можно было посмотреть на ответ сервера, сделаем вот такой минимальный код. Или же воспользуйтесь [-> Виджет для мониторинга подключения `pyjs_log.vue`](#виджет-для-мониторинга-подключения-pyjs_logvue)

```ts
function main() {
    // VVVV Вот тут пишем запросы для сервера VVVV
    //                                          //
    // ^^^^ Вот тут пишем запросы для сервера ^^^^
}

const wbs_obj = new Wbs("ЛюбойТокенКоторыйВыРазрешилиНаСервере", {
    host: "localhost",
    port: 9999,
    callback_onopen: main,
    callback_onmessage: (event: MessageEvent) => {
        // Выводим ответ сервера в консоль
        console.log(<ServerWbsResponse>JSON.parse(event.data), null, 2);
    },
});
```

##### exec

Выполнить произвольную команду. Сервер вернет значение переменной(или выражения) которая находится на последней строке запроса. Это больше сделано для фана, на практике лучше использовать доступные функции на сервере, например потому что их можно дебажить, и они логируются [-> Логирование на сервере](#логирование-на-сервере)

```ts
const command = `
a=2
b=2
c=a+b
c
`;

wbs_obj.send({
    mod: ClientsWbsRequest_Mod.exec,
    h_id: 80,
    body: {
        exec: command, // Команда
    },
});
```

##### import_from_server

Импортировать модули, они будут доступны для всех последующих модификаций `exec`.

```ts
const command = `
import grp
import os
import pwd
import stat
from datetime import datetime
`;

wbs_obj.send({
    mod: ClientsWbsRequest_Mod.import_from_server,
    h_id: 81,
    body: {
        import_sts_exe: command, // Команда
    },
});
```

##### info

Модификация для получения служебной информации о сервере.

```ts
wbs_obj.send({
    mod: ClientsWbsRequest_Mod.info,
    h_id: 82,
    body: {
        id_r: ClientsWbsRequest_GetInfoServer_id.help_allowed, // О чем информацию
        text: undefined, // Дополнительные текст
    },
});
```

- `id_r` - О чем информация:

  - `help_allowed` = Получить информацию(имя, аннотацию типов) о доступных функциях, которые вы можете выполнить на сервере. Структура ответа сервера описана в `DT_HelpAllowed`. [Доступные функции](#доступные-функции).
  - `info_event` = Получить информацию о подписках. [События на сервере](#события-на-сервере).
  - `check_token` = Выполняется автоматически при каждом подключение(переподключение) к серверу.

- `text` - Дополнительные текст. Например, используется для передачи токена.

##### func

Выполнить доступную функцию на сервере. Вот реализация на `Python` сервере [Доступные функции](#доступные-функции)

- Вызвать синхронную функцию `sum`

  ```ts
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.func,
    h_id: 83,
    body: {
        n_func: "getFileFromPath", // Имя функцию которую вызвать
        args: undefined, // Позиционные аргументы
        kwargs: { path: "/home" }, // Именованные аргументы
    },
  });
  ```

- Вызвать асинхронную функцию `os_exe_async`(точно так же как и синхронную)

  ```ts
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.func,
    h_id: 84,
    body: {
        n_func: "os_exe_async", // Имя функцию которую вызвать
        args: ["ls"], // Позиционные аргументы
        kwargs: undefined, // Именованные аргументы
    },
  });
  ```

##### event_create

Про [-> События на сервере](#события-на-сервере)

**Создать** отслеживание "события на севере", с указанной модификацией(`mod`) ,и подписаться на него. "Модификации событий" - нужны чтобы одно и то же событие, можно было отслеживать с разными аргументами. "События сервера" **по умолчанию отключены**, их нужно запускать через `event_create` с указанной "Модификации события". Запущенное "Событие сервера" с указанной "Модификации события", доступно для отслеживания всем аутентифицированным клиентам. "События сервера" с указанной "Модификации события" автоматически остановиться если все клиенты от него отпишутся.

```ts
wbs_obj.send({
    mod: ClientsWbsRequest_Mod.event_create,
    h_id: 85,
    body: {
        n_func: "watchDir", // Имя функции которая отслеживает событие на сервере
        mod: "dubl", // Модификация событие
        args: ["/home"], // Позиционные аргументы
        kwargs: undefined, // Именованные аргументы
    },
});
```

##### event_sub

Про [-> События на сервере](#события-на-сервере)

**Подписаться** на оповещения срабатывания "события на севере"(с указанной модификацией). **Обратите внимание** создать и подписываться на события, можно из разных физических подключений(от разных клиентов), потому что "События сервера" с указанной "Модификации события" существует на сервере, и доступно всем аутентифицированным клиентам.

```ts
wbs_obj.send({
    mod: ClientsWbsRequest_Mod.event_sub,
    h_id: 86,
    body: {
        n_func: "watchDir", // Имя функции которая отслеживает событие на сервере
        mod: "dubl", // Модификация событие
    },
});
```

##### event_unsub

Про [-> События на сервере](#события-на-сервере)

**Отписаться** от оповещения срабатывания "события на севере"(с указанной модификацией). [-> event_create](#event_create)

```ts
wbs_obj.send({
    mod: ClientsWbsRequest_Mod.event_sub,
    h_id: 86,
    body: {
        n_func: "watchDir", // Имя функции которая отслеживает событие на сервере
        mod: "dubl", // Модификация событие
    },
});
```

##### Использование кеша пользователя на стороне клиента

Кеш пользователей храниться в реляционной БД, вот его структура.

Таблица `main`

| user (unique PK) | key (unique PK)          | idkey (unique)             |
| ---------------- | ------------------------ | -------------------------- |
| Пользователь     | Ключ в текстовом формате | ID на данные -> data.idkey |

> По умолчанию будет создан пользователь по имени `base`

Таблица `data`

| idkey (unique PK) | json                  | hash                                         |
| ----------------- | --------------------- | -------------------------------------------- |
| ID данных         | Данные в формате JSON | Хеш данных столбца `json` в формате `sha256` |

Вы можете увидеть, "пользовательский кеш", распределен по ключам. Это удобно, потому что предполагаю, вы будете хранить пользовательские настройки, и темы, а в них все распределено по `ключ:значение`. Благодаря столбцу `hash` можно не задумываться о проблеме лишних обновлениях столбца `json`, он будет обновлен только если `hash` в запросе отличается. Поэтому клиенту доступен только метод `cache_add_key` которые и записывает и обновляет(если хеш разный) ключ.

---

- `cache_add_key` - Создать ключ, с "пользовательским кешем"

  ```ts
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.cache_add_key,
    h_id: 87,
    body: {
        key:"ИмяКлюча"
        value:JSON.stringify(ЗначениеКлюча)
        // Если не указан, то возьмется из конструктора  `new Wbs(user="ИмяПользователя")`, если такого пользователя не существет, то создастся новый.
        // user: "ИмяПользователя"
    }
  });
  ```

- `cache_read_key` - Получить "пользовательский кеш" по указному ключу

  ```ts
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.cache_read_key,
    h_id: 87,
    body: {
        key: "ИмяКлюча",
        // Если не указан, то возьмется из конструктора  `new Wbs(user="ИмяПользователя")`, если такого пользователя не существет, то будет ошибка.
        // user: "ИмяПользователя"
    },
  });
  ```

## Python

### Быстрый старт Python

Интеграция на стороне `Python` основана на `WebSocket`. Через `Python` мы сможем работать с операционной системой.

- **Настройка сервера** - создаем файл `use_server.py`, в нем указываются все настройки.

  ```python
  import asyncio
  from pathlib import Path
  from pywjs.wbs.server import wbs_main_loop
  from pywjs.wbs.handle import WbsHandle
  from pywjs.wbs.logger import ABC_logger, defaultLogger
  from Реализация import МоиФункции, МоиПодписки

  class UserWbsHandle(WbsHandle):
    # Класс с разрешенными функции (Обязательный)
    allowed_func = МоиФункции
    # Класс с "События на сервера"
    allowed_subscribe = МоиПодписки
    # Разрешенные токены для подключения
    allowed_token = set(['ЛюбойТокенКоторыйВыРазрешилиНаСервере'])
    # Путь для кеша пользователей (Обязательный)
    path_user_cache = Path(__file__).parent / 'user_cache.sqlite'
    # Определяем логер. По умолчанию используется https://pypi.org/project/logsmal/ (Обязательный) 
    logger: ABC_logger = defaultLogger(path_to_dir_log=Path(__file__).parent)

  host = "localhost"
  port = 9999

  if __name__ == '__main__':
    asyncio.run(wbs_main_loop(host, port, UserWbsHandle))
  ```

  - `МоиФункции` [-> Доступные функции](#доступные-функции)
  - `МоиПодписки` [-> События на сервере](#события-на-сервере)
  - `path_log` [-> Логирование на сервере](#логирование-на-сервере)
  - `path_user_cache` [-> Кеш пользователей](#кеш-пользователей)

### Доступные функции

Можно использовать как **синхронные**, так и **асинхронные** функции. Для их вызова на стороне клиента используйте модификацию [-> func](#func).

Вот пример полезных функций.

```python
import grp
import os
import pwd
import stat
from datetime import datetime
from pywjs.wbs.allowed_func import AllowWbsFunc
from asyncio import create_subprocess_shell, subprocess


class МоиФункции(AllowWbsFunc):

    # Асинхронная функция
    async def os_exe_async(command: str) -> dict:
        """
        Выполнить асинхронно команды(command) в bash.
        """
        # Выполняем команду
        p = await create_subprocess_shell(
            cmd=command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        # Получить результат выполнения команды
        stdout, stderr = await p.communicate()
        return dict(
            stdout=stdout.decode(),
            stderr=stderr.decode(),
            cod=p.returncode,
            cmd=command,
        )

    # Синхронная функция
    def getFileFromPath(path: str) -> str:
        """
        Получить информацию о файлах и директориях по указанному пути `path`
        """
        res = {}
        for x in os.scandir(path):
            tmp = {}
            type_f = None
            try:
                d: os.stat_result = x.stat()
                tmp['st_size'] = d.st_size  # Размер в байтах
                tmp['date_create'] = datetime.utcfromtimestamp(
                    int(d.st_ctime)).strftime('%Y-%m-%d %H:%M:%S')  # Дата создания
                tmp['date_update'] = datetime.utcfromtimestamp(
                    int(d.st_mtime)).strftime('%Y-%m-%d %H:%M:%S')  # Дата изменения
                tmp['user'] = pwd.getpwuid(d.st_uid).pw_name  # Пользователь
                tmp['group'] = grp.getgrgid(d.st_gid).gr_name  # Группа
                tmp['chmod'] = stat.S_IMODE(d.st_mode)  # Доступ к файлу
            except FileNotFoundError:
                tmp['st_size'] = 0
                tmp['date_create'] = 0
                tmp['date_update'] = 0
                tmp['user'] = 0
                tmp['group'] = 0
                tmp['chmod'] = 0
                type_f = 'file'
            if x.is_file():
                type_f = 'file'
            elif x.is_dir():
                type_f = 'dir'
            tmp['type_f'] = type_f
            res[x.name] = tmp
        return res
```

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

```python
from pywjs.wbs.allowed_func import AllowWbsFunc

class ДругойКласс_N:
  def ИмяФункции():...

class ДругойКласс_1:
  def ИмяФункции():...

class МоиФункции(AllowWbsFunc,ДругойКласс_1,ДругойКласс_N):
  def ИмяФункции():...
```

Для того чтобы обратиться к функции из `ДругойКласс_N` через модификатор `func` - нужно указать имя класса, а потом через точку имя функции (`ДругойКласс_N.ИмяФункции`). Для функций из класса `МоиФункции` ,не нужно указывать имя класса, пишите сразу `ИмяФункции`.

#### Функция в режиме транзакции

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

Пример простой функции, которую рационально использовать в транзакции. Например, нужно прочитать какой-нибудь указанный файл, но он может не существовать, в таком случае, нужно уведомить клиента, о том что, не возможно корректно выполнить эту команду. Клиент в свою очередь, в функции `rollback` обрабатывает такую ситуацию, и уведомляет пользователя о том что такого файла нет.

Сервер:

```python
from pywjs.wbs.allowed_func import Transaction, AllowWbsFunc

class МоиФункции(AllowWbsFunc):

    @Transaction._(rollback=lambda: '!! Произошёл rollback на стороне сервера !!')
    async def readFile(path: str:
        """
        Прочесть файл
        """
        p = Path(path)
        if not p.exists():
            raise Transaction.TransactionError('Файл не существует.')
        else:
            return p.read_text()
```

Клиент:

```ts
const path = "/home/ФайлКоторогоНет";
wbs_obj.send_transaction(
    // Это основной запрос
    {
        mod: ClientsWbsRequest_Mod.func,
        h_id: 99,
        body: {
            n_func: "readFile", // Имя функцию которую вызвать
            args: [path],       // Позиционные аргументы
        },
    },
    // Это функция `rollback`
    <TRollback>(
        error_code: TRollbackErrorCode,
        h_id: number,
        uid_c: number,
        res_server_json: ServerWbsResponse
    ) => {
        alter(`Rollback: ошибка обработки файл "${path}"`);
        if (error_code == TRollbackErrorCode.error_server) {
            // Текст ошибки на сервере
            alter(res_server_json.error);
        }
    }
);
```

- Шаблон транзакционной функции:

  ```python
  @Transaction._(rollback=ФункцияДляОтката)
  async def ИмяФункции(*args, **kwargs):
    ... # Что то делаем.
    if ... : # Что то пошло не так.
        raise Transaction.TransactionError('СообщениеДляКлиента') # Вызываем ошибку в транзакции.
    return ... # Если все хорошо, возвращаем ответ.
  ```

  - `ФункцияДляОтката`(Можно не указывать) - Эта функция вызовется, если произошло любое не обработанное исключение. Результат этой функции будет передан клиенту, вмести с описанием ошибки.
  - `СообщениеДляКлиента` - Как написано выше, `ФункцияДляОтката` - вызывается при любом не обработанном исключение, но лучше обрабатывать все исключения в этой же функции, для того чтобы логика обработки исключений не расползалась по всему проекту. Исключение `Transaction.TransactionError` нужно вызвать если это уже не решаемая проблема (например у клиента нет `root` доступа, и пока он его не передаст пароль, выполнение не может быть продолжено), то тогда, лучше обработать такое исключение в `ФункцияДляОтката`, и в нем же создать сообщения для клиента - чтобы он передал пароль от `root` пользователя.

#### Стандартные Доступные Функции

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

Вы можете подключать "Стандартные Доступные Функции"(`StdAllowWbsFunc`) путем множественного наследования [-> Используйте множественное наследование для расширения доступных функций](#используйте-множественное-наследование-для-расширения-доступных-функций). Вот пример:

```python
from pywjs.wbs.allowed_func import AllowWbsFunc,StdAllowWbsFunc


class МоиФункции(AllowWbsFunc,StdAllowWbsFunc): 
  ...
```

### События на сервере

"События сервера" - Функции которые выполняются в бесконечном цикле, и могут отправлять сообщение клиенту.

- Пример отслеживания "События сервера" - переименование,создание,удаление файлов и директорий в указанном пути.

  ```python
  import os
  from datetime import datetime
  from pywjs.wbs.subscribe import UserWbsSubscribe
  from asyncio import create_subprocess_shell, subprocess

  class МоиПодписки(UserWbsSubscribe):
    async def watchDir(self_, path: str):
        """
        Отслеживание изменений файлов и директорий в пути `path`
        """
        pre = [] # Инициализация локальных переменных
        while await self_.live(sleep=2): # Бесконечный не блокирующий цикл событий, которые будет выполнятся через каждые `sleep`
            f = os.listdir(path) # Отслеживания события
            if pre != f: # Условия срабатывания события
                pre = f
                await self_.send(f) # Отправка сообщения всем подписчикам для указанной модификации
  ```

  Шаблон отслеживания "события на сервере"

  ```python
  async def ИмяФункции(self_, path: str):
    ... # Инициализация локальных переменных
    while await self_.live(sleep=СколькоЖдать): # Бесконечный не блокирующий цикл событий, которые будет выполнятся через каждые `sleep`
        ... # Отслеживания события
        if ... : # Условия срабатывания события
            await self_.send(...)  # Отправка сообщения всем подписчикам для указанной модификации
  ```

### Кеш пользователей

В большинстве десктоп приложений, у пользователей есть персональные данные, настройки, темы оформления. Все это является "кешем пользователя".
Для легкой совместимостью с разными платформами, выбрано СУБД SQLite. Вам не нужно писать SQL запросы, всё взаимодействие через готовые функции.

Для того чтобы начать использовать "кеш пользователя" на сервере, нужно указать путь для БД которая будет его хранить.

```python
class UserWbsHandle(WbsHandle):
    path_user_cache= Path(__file__).parent / 'user_cache.sqlite'
```

На этом настройка кеширования на сервера заканчивается, спасибо за внимание. Как вы видите для использования кеширования пользователя, нужна одна строка кода. [-> **Использование кеша пользователя на стороне клиента**](#использование-кеша-пользователя-на-стороне-клиента)

Часто бывают случаи когда нужно установить кеш по умолчанию, то есть записи должны быть добавлены в БД вмести с созданием таблицы. Для этого есть удобная функция `init_user_cache`, в которой вы указываете записей в виде `JSON`:

```python
class UserWbsHandle(WbsHandle):
    path_user_cache= Path(__file__).parent / 'user_cache.sqlite'
    # Заполнить кеш пользователя значениями по умолчанию
    def init_user_cache(self) -> dict:
      return {
        "ИмяПользователя": {
          'Ключ': 'Значение'
        }
      }
```

Что еще нужно знать про пользовательский кеш:

- Базы данных с пользовательским кешем, **автоматически восстанавливается, даже если её удалят во время работы сервера** -> Если БД не существует(по пути `path_user_cache`) на момент вставки или чтения, то создаться новая БД, и выполнятся все настройки по умолчанию, а потом уже выполниться запрос, на вставку или чтения. Поэтому можно быть уверенным что БД всегда будет существовать в момент выполнения `cache_add_key` / `cache_read_key`
- По умолчанию будет создан пользователь по имени `base`, это пользователь по умолчанию.
- По умолчанию будет создан пользователь по имени `app`, в этом пользователи храниться глобальные настройки проекта. Ключи, которые начинаются на `_`(нижнее подчеркивание) зарезервированы для системного пользования, и вы не можете их изменить через функцию `cache_add_key`, вы можете только их прочитать через функцию `cache_read_key` [-> Структура пользователя `app`](#структура-пользователя-app).(Пока нет ни каких зарезервированных для системы ключей, это ограничение - задел на бедующее)

### Логирование на сервере

По умолчанию для логирование на сервере использует [logsmal](https://pypi.org/project/logsmal/), её создал тот же автор, что и `PywJs`. **Его основное предназначения для отладки и дебага программы**, если вам нужно что то большее, то можете использовать `logging` and `loguru`.

```python
from pywjs.wbs.logger import ABC_logger, defaultLogger,EmptyLogger

class UserWbsHandle(WbsHandle):
  # Определяем логер. По умолчанию используется https://pypi.org/project/logsmal/
  logger: ABC_logger = defaultLogger(path_to_dir_log=Path(__file__).parent)
```

Eсли вам нужно отключить логирование(игнорировать все вызовы логера), то укажите `logger=EmptyLogger`

#### Переопределение логгера по умолчанию

Если вам нужен другой логгер, то реализуйте абстрактный класс `wbs.wbs_logger.ABC_logger`

#### В чем удовлетворительность логирования через `logsmal` ?

- Использование логера в доступных функциях, и событиях на сервере. Настройка логера в главе [-> Быстрый старт Python](#быстрый-старт-python)

  ```python
  # Импортируем переопределенный логер
  import wbs.wbs_server as baseWbs
  # Выполняем логирование
  baseWbs.logger.debug("Сообщение",['Флаг_1','Флаг_2','Флаг_N'])
  baseWbs.logger.info("Сообщение",['Флаг_1','Флаг_2','Флаг_N'])
  baseWbs.logger.success("Сообщение",['Флаг_1','Флаг_2','Флаг_N'])
  baseWbs.logger.warning("Сообщение",['Флаг_1','Флаг_2','Флаг_N'])
  baseWbs.logger.error("Сообщение",['Флаг_1','Флаг_2','Флаг_N'])
  ```

- В чем плюсы:
  1. Лог сообщение в консоли обрезается, а полный текст записывается в файл. Это позволяет не засорять консоль огромными сообщениями.
  2. По умолчанию в `Linux` есть подсветка лог сообщений.
  3. Полные и красивое описание исключений. Краткий текст исключений передастся в консоль с указанием `ERROR_LOG`, а полный текст, стек вызова, локальные переменен запишутся в файл `detail_error.log`, и по `ERROR_LOG` вы можете найти это исключение.
  4. Автоматическое очистка лога файлов, при достижении размера `10mb`(можно указать не отчищать, а сжимать файл в архив).
  5. В лог файл, строчки записываются в формате JSON. Вы можете без дополнительных преобразований, хранить их, например в `Elasticsearch` для аналитики.

### JSON Схемы

- Отправка на сервер(к Python) = Структура запроса `pywjs.wbs.schema.ClientsWbsRequest`
- Ответ от сервер(к Python) = Структура ответа `pywjs.wbs.schema.ServerWbsResponse`

### Установка PywJs программы

В обычном понимание, установка программа на `PywJs` не нужна. Что `html` файл, сразу готов к запуску, что `python`(при наличии зависимых модулей) сразу готов запуску. Но все же, для пользователей которые, не знаю кто такой `pip`, и как открывать консоль, нуждаются в некоторой автоматизации. **Обязательно посмотрите на [-> Требуемый шаблон для программы](#требуемый-шаблон-для-программы)**

1. Шаг раз - пользователь исполняет `auto_install.py`:

   1. Создать виртуальное окружение для `Python`.
   2. Установить зависимости из файла `server/pyproject.toml`, в виртуальное окружение. Используется `poetry` а не стандартный `pip` , потому что  `poetry` позволяет синхронизировать зависимости, например, он удаляет модули которые установлены,  но которых нет `pyproject.toml`, это используется в [-> Автообновление `pywjs` программы](#автообновление-pywjs-программы)
   3. Создать файл `auto_uninstall.py`, для удаления программы.
   4. Создать файл `auto_run.py`, для запуска программы.
   5. Создать файл `.gitignore`, для игнорирования.
   6. Создать файл `auto_update.py`, для автоматического обновления. [-> Автообновление `pywjs` программы](#автообновление-pywjs-программы)

2. Шаг два - пользователь исполняет `auto_run.py`:

   1. Запускается `index.html` в браузере по умолчанию.
   2. Запускается `main.py`.

#### Требуемый шаблон для программы

- `ИмяПрограммы`
  - `client` - Для фронта
    - ...
    - `index.html`
    - ...
  - `server` - Для бека
    - ...
    - `main.py` - Место для [WbsHandle](#быстрый-старт-python)
    - `pyproject.toml` - Список зависимостей для `python`, используемый в `poetry`
    - ...
  - `auto_install.py` - Установка программы. Создать это файл, и скопировать код из `https://github.com/denisxab/py_wjs/blob/main/builder/auto_install.py`
  - ...

#### Автообновление `pywjs` программы

Пойдем "Быстрее, выше, сильнее" и дадим пользователям, которые так и не познакомились с `pip`-ом и `git`-ом, возможность автоматического обновления `pywjs` программы. Но все же, пользователю придется сделать лишнее движение, это установить `git` ... Я бы рад написать свою независимую систему управления версий из коробки, и тогда бы пользователям не нужно было бы делать лишний шаг, но меня заклюют хейтеры, за то что я изобретаю велосипед, и в этом, я буду с ними согласен. Поэтому пользователю нужно объяснить как установить git.

**Подразумевается что вы строго соблюдаете [-> Требуемый шаблон для программы](#требуемый-шаблон-для-программы)**

Две вещи которые происходят в авто обновление программы:

- Синхронизация файлов и директорий через Git.д
- Синхронизация версией модулей в виртуальное окружение `python`, а также скачивание новых модулей, и удаление старых. Для этого используется библиотека `poetry`, и в частности файл `pyproject.toml`.

Проверка на необходимость обновления, происходит при выполнении `auto_run.py`.

1. В этот момент выполняется команда `git diff`, если есть различия то выполняется синхронизация.

    ```bash
    git diff ЛокальнаяВетка origin/УдаленнаяВетка --raw
    ```

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

2. Если произошла синхронизация проекта, то тогда произойдет синхронизация зависимостей для виртуального окружения.

**После выполнения обновления - запускается программа**.

### Особый подходы

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

#### Как открыть любой файл в браузере ?

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

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

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

Пример **готовых** функций, для работы с символьными фалами:

- `getPath` - Функция для получения текущего пути к `html` файлу. Это нужно для того, чтобы знать куда создавать символьные файлы. Если открывать `html` файлы двойным кликом, то url будет равен абсолютному пути к этому `html` файлу - `file://АбсольтныйПутьДля.html`, поэтому можно узнать, куда динамически помещать символьные файлы, в зависимости от того, где запущен `html` файл. Но если вы запускаете `html` через всякие `live preview` или через `dev server` то тогда у вас не будет абсолютного пути к файлу, для такого случая нужно указать путь по умолчанию `getPath({default_path:'ПутьПоУмолчанию'})`.

  ```ts
  import { getPath } from "wbs/wbs_std";
  ```

- Создать символьную ссылку на файл через `Python`. Для этого используем стандартную доступную функцию `StdAllowWbsFunc.createLinkToFile`. [-> Тут написано как подключить стандартные доступные функции](#стандартные-доступные-функции)

  ```js
  wbs_obj.send({
    mod: ClientsWbsRequest_Mod.func,
    h_id: 99,
    body: {
      n_func: "StdAllowWbsFunc.createLinkToFile",
      kwargs: {
        pathDirLinks: getPath(), // Путь к директоории в которой хранятся символьные ссылки
        pathFile: ...            // Путь к файлу на который нужно сделать символьную ссылку
        extendsFile: ...         // Разширение для файла
      }
    }
  });
  ```

- Использование в HTML, это краткий, и условный пример. Вам нужно будет реализовать механизм получения пути на символьный файл, от функции `StdAllowWbsFunc.createLinkToFile` [на `vue.js` этот механизм уже реализован через вычисляемые значения](#как-пользоваться-во-vuejs).

  ```html
  <!-- Если это Фото -->
  <img src="Путь.png" />
  <!-- Если это PDF -->
  <iframe src="Путь.pdf" frameborder="0"></iframe>
  <!-- Если это Текстовый файл -->
  <div id="textDiv"></div>
  <script>
    /* Прочитать локальный ссылочный файл */
    function readFile(pathLink) {
      fetch(pathLink)
        .then((response) => response.text())
        .then((text) => {
            // @ts-ignore
            this.textFile = text;
        });
    }
    textDiv = readFile("Путь.txt");
  </script>
  ```

# Для гуру

Задач нет

- Првоерить автообновление программы

