Многие из вас наверняка слышали про сервисы, позволяющие делится скриншотами одним нажатием кнопки. Общая концепция следующая: жмёшь хоткей, выделяешь область экрана, в буфер обмена получаешь ссылку на скриншот, который автоматически загружается на какой-то
Мы объединили две эти идеи и сделали проект, обладающий следующими фичами:
- Публикация скриншотов и исходников по нажатию хоткея
- Open Source — можете форкнуть / поднять на своём сервере / добавить новые фичи
- Прямая ссылка на изображения, отсутствие рекламы
- Кроссплатформенность
- Устойчивость к высоким нагрузкам (хостимся в облаке)
Сервис состоит из следующих компонент:
- Клиентское приложение
- Серверный демон, принимающий скриншоты
- Веб интерфейс
Клиентское приложение делает скриншот и передаёт его на сервер. Серверный демон получает файл, генерирует ему уникальное имя и записывает в определённую папку. Все полученные файлы становятся доступны по http протоколу — для этих целей используется nginx. Так же nginx + php & fastcgi используется для работы библиотеки подсветки синтаксиса.
Клиентское приложение
Для разработки клиентского приложения был выбран фреймворк Qt. Преимущества — хорошая документация, кроссплатформенность, большое количество библиотек. И вообще он нам нравится. Кроме того, нам пришлось задействовать библиотеку qxt для кроссплатформенной работы с глобальными хоткеями.
Приложение состоит из следующих классов:
- Application
- Network
- ConfigWidget
- ImageSelectWidget
- LanguageSelectDialog
- ScanHotkeyDialog
Application — тут реализована работа с глобальными хоткеями, получение скриншотов, работа с треем и буфером обмена.
Network — работа с сетью, т. е. подключение к серверу, отправка данных по простому http-подобному протоколу, получение от сервера ссылки.
ConfigWidget, ImageSelectWidget, LanguageSelectDialog, ScanHokeyDialog — простые классы, отвечающие за окно конфига, окно выбора изображения, языка программирования и настройки горячих клавишь.
Во время инициализации приложения происходит следующее. Вначале проверяем, запущенно ли уже приложение. Из кросплатформенных решений самым простым вариантом проверки оказалось запустить QLocalServer, и пытаться подключиться к нему при старте. Если подключение не удалось — значит это новый запуск и мы работаем, в противном случае выходим.
QLocalSocket socket;
socket.connectToServer(APP_NAME);
if (socket.waitForConnected(500)) {
qDebug() << "Application allready launched!";
return false;
}
_localServer = new QLocalServer(this);
if (!_localServer->listen(APP_NAME)) {
QLocalServer::removeServer(APP_NAME);
_localServer->listen(APP_NAME);
}
Затем регистрируем хоткеи. Используя libqxt с хоткеями работать довольно просто. Нужно создать хоткей, указать для него комбинацию клавиш и связать со слотом, который выполнится по нажатию на данный хоткей:
_shortcutScreenFull = new QxtGlobalShortcut;
if (!_shortcutScreenFull->setShortcut(QKeySequence(fullHotkey)))
qDebug() << "Error activating hotkey:" << fullHotkey;
connect(_shortcutScreenFull, SIGNAL(activated()), this, SLOT(processScreenshotFull()))
Сама процедура получения скриншота выглядит так. Получаем скриншот, сохраняем его в необходимом формате в QBuffer, получаем массив байт QByteArray и отправляем на сервер:
QPixmap pixmap = QPixmap::grabWindow(QApplication::desktop()->winId());
QByteArray imageBytes;
QBuffer buffer(&imageBytes);
buffer.open(QFile::WriteOnly);
pixmap.save(&buffer, imagetype.toAscii().constData());
buffer.close();
_network->upload(imageBytes, imagetype);
Протокол отправки файла клиентом на сервер следующий. Клиент отправляет:
proto=pastexen
version=1.0
type=jpg
size=142034
binary_data
Где jpg — тип файла; 142034 — размер файла в байтах; binary_data — содержимое файла
После получения файла сервер отвечает:
proto=pastexen
version=1.0
url=http://host.tst/file1.jpg
url — прямая ссылка на файл или на страницу с оформленным текстом с подсветкой синтаксиса
В методе upload класса Network подключаемся к серверу, формируем пакет согласно протоколу и передаём его:
_socket.connectToHost(_serverAddr, 9876);
_socket.waitForConnected();
QByteArray arr;
arr.append("proto=pastexenn");
arr.append("version=0.2n");
arr.append("type=" + type + "n");
arr.append(QString("size=%1nn").arg(data.size()));
arr.append(data);
_socket.write(arr);
Как только получаем ответ, извлекаем из него ссылку, и кидаем сигнал linkReceived:
const QByteArray arr = _socket.readAll();
const QString link = getValue(arr, "url");
emit linkReceived(link);
_socket.disconnectFromHost();
Ловим сигнал в классе Application, копируем полученную ссылку в буфер обмена, показываем сообщение в трее:
QApplication::clipboard()->setText(link);
_trayIcon->showMessage(tr("Done!"), tr("File shared!"), QSystemTrayIcon::Information, 6500);
Похожим образом реализована отправка исходного кода.
Серверная часть
Серверный демон был так же сделан с использованием Qt. Мы протестировали получившееся решение под нагрузкой (1000 одновременно передающихся файлов) и решили что оно нас устроит.
Сервер реализован на основе пула потоков. Все подключенные клиенты распределяются между имеющимися потоками, работа с клиентами происходит в асинхронном режиме. Сохранение полученных данных в файлы так же осуществляется в отельном потоке.
В приложении используются классы:
- Server
- ThreadPool
- Saver
- Socket
В классе Server создаётся tcp server (QTcpServer), обрабатываются новые подключения. Для подключившегося клиента создаётся Socket, который будет выполнятся в одном из потоков, предоставляемых ThreadPool_ом.
auto socket = _server.nextPendingConnection();
new Socket(socket, ThreadPool::getNextThread(), it.value());
Кроме того, при подключении происходит проверка, не превысил ли пользователь лимит.
В конструкторе класса Socket, он переносится в переданный ему поток выполнения (т. е. обработка всех событий, связанных с этим классом будет происходить в event-loop_e, работающем в другом потоке). Для этого используется функция moveToThread.
Так же внутри Socket обрабатываются пришедшие данные, отправляется сигнал на сохранение (emit saveFile). Этот сигнал обработает класс Saver, который запущен в другом потоке. После того, как Saver сохранит всё в файл — он пошлёт сигнал, который обработает Socket и отправит ссылку на файл клиенту.
Хостинг
Проект с самого начала планировался как open-source. И когда он был практически готов, встал вопрос о
На сервер был установлен debian, nginx, настроены rc скрипты и автоматический рестарт с использованием monit.
Будущее проекта
В следующей версии программы мы планируем сделать следующие фичи:
- Добавить функцию перевода по нажатию одной кнопки из буфера обмена с использованием сервиса translate, например от Google.
- Превью картинок.
- Mac сборка (если кто-то умеет собирать qt приложения под маком, помощь бы не помешала)
- Интернационализаця
- Возможно вы сможете предложить что-то ещё
Ссылки
pastexen.com — официальный сайт проекта
github.com/bakwc/Pastexen — исходники на гитхабе
scalaxy.ru — компания “Скалакси”, предоставившая проекту облачный
Автор: bak