Pull to refresh

Сервис шаринга скриншотов и кода

Reading time5 min
Views10K
Многие из вас наверняка слышали про сервисы, позволяющие делится скриншотами одним нажатием кнопки. Общая концепция следующая: жмёшь хоткей, выделяешь область экрана, в буфер обмена получаешь ссылку на скриншот, который автоматически загружается на какой-то хостинг. Так же многие из вас наверняка пользовались другими сервисами — позволяющими опубликовать фрагмент исходного кода и получить ссылку на страничку, отображающую этот исходник с подсветкой синтаксиса.

Мы объединили две эти идеи и сделали проект, обладающий следующими фичами:

  • Публикация скриншотов и исходников по нажатию хоткея
  • 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 already 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=pastexen\n");
  arr.append("version=0.2\n");
  arr.append("type=" + type + "\n");
  arr.append(QString("size=%1\n\n").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 — компания “Скалакси”, предоставившая проекту облачный хостинг
Tags:
Hubs:
+16
Comments56

Articles