РЕАЛИЗАЦИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
4.1 Приложение
Приложение реализовано по схеме клиент-сервер с асинхронным обменом данными между браузерами клиентов и сервером приложения по протоколу WebSockets. При построении приложения использована архитектура Model-View-Controller с «тяжелой», содержащей логику клиентской частью.
Структурно серверная часть приложения состоит из одного решения (solution) Microsoft Visual Studio и двух входящих в него проектов:
— Pad.Domain. Отвечает за связь с БД, загрузку и сохранение изменений, реализует шаблон проектирования «репозиторий»;
— Pad.WebUI. Отвечает за взаимодействие с пользователем, пользовательский интерфейс и логику.
Клиентская часть отвечает за обеспечение синхронизации клиентов (серверная часть только хранит изменения и пересылает их другим клиентам) и взаимодействие с пользователями.
За синхронизацию отвечает реализация впервые описанного в [12] и [13] алгоритма основанного на допустимости операционального преобразования. По сравнению с оригинальным алгоритмом реализация доработана:
— Поддерживает атомарные многосимвольные операции, например вставку текста из буфера обмена (оригинальный алгоритм поддерживает всего две операции: вставку одного символа и удаление одного символа);
— Реализует структуру текста строка:символы строки (оригинальный алгоритм поддерживает исключительно одномерный массив, где все символы находятся в одной строке, что плохо сочетается с API CodeMirror);
— Поддерживает операции форматирования текста;
Поддерживает вход пользователей в середине сесии и началу работы не с пустого документа;
— Содержит другие, менее значимые изменения, направленные на интеграцию алгоритма с API CodeMirror.
При любом изменении в тексте документа (ввод символов, их удаление, вставка, перемещение блоков текста, изменение форматирования) срабатывает обработчик события “change” CodeMirror, к данным изменения, предоставляемым API CodeMirror добавляется информация об авторе изменения, времени (не используется при определении порядка изменений) и статус-вектор: словарь (набор пар ключ-значение, где ключ – идентификатор пользователя, в т.ч. и свой, а значение – количество известных операций, сгенерированных этим пользователем). Этот набор данных в формате JSON отправляется на сервер, где преобразовывается в удобный для хранения в базе данных формат (при этом статус-вектор остается в формате JSON из соображений производительности), сохраняется и перенаправляется другим подключенным клиентам, у которых открыт тот же документ и которые имеют право на чтение (и, соответственно, получение новых изменений с сервера). Принятые изменения обрабатываются в соответствии с алгоритмом операционального преобразования, чтобы исключить рассинхронизацию клиентов, и применяются к документу. Для определения порядка применения изменений сравниваются их статус-векторы, сохраненные ранее.
Каждый вводимый пользователем символ подсвечивается т.н. авторским цветом, который пользователь может выбирать произвольно из полной палитры цветов. Авторские цвета служат для наглядной индикации авторства вносимых изменений и мест в документе, куда эти изменения были внесены. Авторские цвета с того или иного участка текста (или из всего документа) могут быть убраны нажатием кнопки «W» на панели инструментов. При отключении пользователя от сервера его пользовательский цвет изменяется на менее насыщенный.
Для каждого документа существует отдельный чат, предназначенный для координации авторов. Отправка сообщения происходит по кнопке «Enter».
Каждый клиент поддерживает актуальный список пользователей, у которых открыт текущий документ. Извещения о подключении или отключении пользователей рассылает сервер. Список пользователей можно увидеть в верхней части правой колонки страницы редактирования документа.
Приложение реализует гибкую систему ролей, позволяющую для каждого пользователя отдельно устанавливать права на те или иные действия, в том числе раздельно на просмотр и редактирование документа, просмотр и отправку сообщений в чат, создание документа и т.д. По умолчанию вновь зарегистрированный пользователь имеет все права кроме доступа к административным функциям, в дальнейшем можно принудительно понизить или понизить уровень доступных привилегий. При необходимости число настраиваемых прав может быть увеличено вплоть до, например, доступа к определенным функциям форматирования текста.
Приведем несколько фрагментов кода серверной части приложения, отвечающих за взаимодействие между клиентами, и содержащихся в контроллере XSocketsDocumentController. Обработка изменения в документе:
[Authorize(Roles = "CanEditDocuments")]
public void ChangeSetFromClient(ChangeSetModel cs)
{
var json = new DataContractJsonSerializer(typeof(Jsonchange.RootObject));
var change = (Jsonchange.RootObject)json.ReadObject(new ding.UTF8.GetBytes(cs.Change)));
var dbChange = ChangeFromJsonchange(change);
_repository.AddChange(Document, dbChange);
this.SendTo(u => u != this && u.Document == Document, change, "ChangeSetFromServer");
}
Перед выполнением обработчика проверяется, может ли пользователь редактировать документ (принадлежит ли к соответствующей роли) с помощью атрибута [Authorize]. Затем из формата JSON данные десериализуются в объект соответствующей структуры, преобразовываются в формат для сохранения в БД и записываются в базу. Исходное изменение без изменений отправляется другим клиентам, у которых открыт тот же документ.
Сообщение чата:
[Authorize(Roles = "CanWriteToChat")]
public void ChatMessage(ITextArgs msg)
{
var json = new DataContractJsonSerializer(typeof(JsonMessage));
var tempMessage = (JsonMessage)json.ReadObject(new (Encoding.UTF8.GetBytes(msg.data)));
tempMessage.Text = tempMessage.Text.Replace("\n", "
");
var message = new ChatMessage
{
AuthorID = tempMessage.Author,
DocumentID = Document,
Text = tempMessage.Text,
Time = DateTime.Now
};
_repository.AddMessage(message);
tempMessage.Author = FindById(message.AuthorID).UserName;
this.SendTo(u => u.Document == Document && u. CanReadChat == true, tempMessage, FromServer");
}
Здесь использован несколько иной подход. В целях устойчивости к некорректному (возможно, умышленному) поведению клиента в функцию передается только текст сообщения, остальные данные (авторство, время, принадлежность к документу) заполняются на стороне сервера. Кроме того, явно проверяется, может ли получающий сообщение пользователь читать чат (в ChangeSetFromClient() проверка была опущена, так как пользователи, которые не имеют прав на чтение документа, просто не смогут подключиться к серверу).
4.2 Пользовательский интерфейс
Пользовательский интерфейс приложения реализован средствами ASP.Net MVC 5 с помощью Razor разметки в .cstml файлах. При реализации интерфейса использован фреймворк Twitter Bootstrap 3. На рисунках 4.1-4.6 показаны некоторые страницы приложения.
Рисунок 4.1 — Главная страница приложения
Рисунок 4.2 — Главная страница приложения на дисплее с маленьким разрешением
Рисунок 4.3 — Главная страница приложения на дисплее с маленьким разрешением, меню открыто
При необходимости на странице просмотра документа, показанной на рис. 4.4 может быть скрыт список пользователей или вся правая панель целиком, что оставит больше места для самого документа.
Рисунок 4.4 — Страница просмотра документа
Рисунок 4.5 — Страница просмотра списка пользователей
Рисунок 4.6 — Страница управления правами пользователя
4.1 Приложение
Приложение реализовано по схеме клиент-сервер с асинхронным обменом данными между браузерами клиентов и сервером приложения по протоколу WebSockets. При построении приложения использована архитектура Model-View-Controller с «тяжелой», содержащей логику клиентской частью.
Структурно серверная часть приложения состоит из одного решения (solution) Microsoft Visual Studio и двух входящих в него проектов:
— Pad.Domain. Отвечает за связь с БД, загрузку и сохранение изменений, реализует шаблон проектирования «репозиторий»;
— Pad.WebUI. Отвечает за взаимодействие с пользователем, пользовательский интерфейс и логику.
Клиентская часть отвечает за обеспечение синхронизации клиентов (серверная часть только хранит изменения и пересылает их другим клиентам) и взаимодействие с пользователями.
За синхронизацию отвечает реализация впервые описанного в [12] и [13] алгоритма основанного на допустимости операционального преобразования. По сравнению с оригинальным алгоритмом реализация доработана:
— Поддерживает атомарные многосимвольные операции, например вставку текста из буфера обмена (оригинальный алгоритм поддерживает всего две операции: вставку одного символа и удаление одного символа);
— Реализует структуру текста строка:символы строки (оригинальный алгоритм поддерживает исключительно одномерный массив, где все символы находятся в одной строке, что плохо сочетается с API CodeMirror);
— Поддерживает операции форматирования текста;
Поддерживает вход пользователей в середине сесии и началу работы не с пустого документа;
— Содержит другие, менее значимые изменения, направленные на интеграцию алгоритма с API CodeMirror.
При любом изменении в тексте документа (ввод символов, их удаление, вставка, перемещение блоков текста, изменение форматирования) срабатывает обработчик события “change” CodeMirror, к данным изменения, предоставляемым API CodeMirror добавляется информация об авторе изменения, времени (не используется при определении порядка изменений) и статус-вектор: словарь (набор пар ключ-значение, где ключ – идентификатор пользователя, в т.ч. и свой, а значение – количество известных операций, сгенерированных этим пользователем). Этот набор данных в формате JSON отправляется на сервер, где преобразовывается в удобный для хранения в базе данных формат (при этом статус-вектор остается в формате JSON из соображений производительности), сохраняется и перенаправляется другим подключенным клиентам, у которых открыт тот же документ и которые имеют право на чтение (и, соответственно, получение новых изменений с сервера). Принятые изменения обрабатываются в соответствии с алгоритмом операционального преобразования, чтобы исключить рассинхронизацию клиентов, и применяются к документу. Для определения порядка применения изменений сравниваются их статус-векторы, сохраненные ранее.
Каждый вводимый пользователем символ подсвечивается т.н. авторским цветом, который пользователь может выбирать произвольно из полной палитры цветов. Авторские цвета служат для наглядной индикации авторства вносимых изменений и мест в документе, куда эти изменения были внесены. Авторские цвета с того или иного участка текста (или из всего документа) могут быть убраны нажатием кнопки «W» на панели инструментов. При отключении пользователя от сервера его пользовательский цвет изменяется на менее насыщенный.
Для каждого документа существует отдельный чат, предназначенный для координации авторов. Отправка сообщения происходит по кнопке «Enter».
Каждый клиент поддерживает актуальный список пользователей, у которых открыт текущий документ. Извещения о подключении или отключении пользователей рассылает сервер. Список пользователей можно увидеть в верхней части правой колонки страницы редактирования документа.
Приложение реализует гибкую систему ролей, позволяющую для каждого пользователя отдельно устанавливать права на те или иные действия, в том числе раздельно на просмотр и редактирование документа, просмотр и отправку сообщений в чат, создание документа и т.д. По умолчанию вновь зарегистрированный пользователь имеет все права кроме доступа к административным функциям, в дальнейшем можно принудительно понизить или понизить уровень доступных привилегий. При необходимости число настраиваемых прав может быть увеличено вплоть до, например, доступа к определенным функциям форматирования текста.
Приведем несколько фрагментов кода серверной части приложения, отвечающих за взаимодействие между клиентами, и содержащихся в контроллере XSocketsDocumentController. Обработка изменения в документе:
[Authorize(Roles = "CanEditDocuments")]
public void ChangeSetFromClient(ChangeSetModel cs)
{
var json = new DataContractJsonSerializer(typeof(Jsonchange.RootObject));
var change = (Jsonchange.RootObject)json.ReadObject(new ding.UTF8.GetBytes(cs.Change)));
var dbChange = ChangeFromJsonchange(change);
_repository.AddChange(Document, dbChange);
this.SendTo(u => u != this && u.Document == Document, change, "ChangeSetFromServer");
}
Перед выполнением обработчика проверяется, может ли пользователь редактировать документ (принадлежит ли к соответствующей роли) с помощью атрибута [Authorize]. Затем из формата JSON данные десериализуются в объект соответствующей структуры, преобразовываются в формат для сохранения в БД и записываются в базу. Исходное изменение без изменений отправляется другим клиентам, у которых открыт тот же документ.
Сообщение чата:
[Authorize(Roles = "CanWriteToChat")]
public void ChatMessage(ITextArgs msg)
{
var json = new DataContractJsonSerializer(typeof(JsonMessage));
var tempMessage = (JsonMessage)json.ReadObject(new (Encoding.UTF8.GetBytes(msg.data)));
tempMessage.Text = tempMessage.Text.Replace("\n", "
");
var message = new ChatMessage
{
AuthorID = tempMessage.Author,
DocumentID = Document,
Text = tempMessage.Text,
Time = DateTime.Now
};
_repository.AddMessage(message);
tempMessage.Author = FindById(message.AuthorID).UserName;
this.SendTo(u => u.Document == Document && u. CanReadChat == true, tempMessage, FromServer");
}
Здесь использован несколько иной подход. В целях устойчивости к некорректному (возможно, умышленному) поведению клиента в функцию передается только текст сообщения, остальные данные (авторство, время, принадлежность к документу) заполняются на стороне сервера. Кроме того, явно проверяется, может ли получающий сообщение пользователь читать чат (в ChangeSetFromClient() проверка была опущена, так как пользователи, которые не имеют прав на чтение документа, просто не смогут подключиться к серверу).
4.2 Пользовательский интерфейс
Пользовательский интерфейс приложения реализован средствами ASP.Net MVC 5 с помощью Razor разметки в .cstml файлах. При реализации интерфейса использован фреймворк Twitter Bootstrap 3. На рисунках 4.1-4.6 показаны некоторые страницы приложения.
Рисунок 4.1 — Главная страница приложения
Рисунок 4.2 — Главная страница приложения на дисплее с маленьким разрешением
Рисунок 4.3 — Главная страница приложения на дисплее с маленьким разрешением, меню открыто
При необходимости на странице просмотра документа, показанной на рис. 4.4 может быть скрыт список пользователей или вся правая панель целиком, что оставит больше места для самого документа.
Рисунок 4.4 — Страница просмотра документа
Рисунок 4.5 — Страница просмотра списка пользователей
Рисунок 4.6 — Страница управления правами пользователя