...

пятница, 4 октября 2013 г.

[Из песочницы] Пишем простой аналог клиента Яндекс.Диск, но под Linux

Всем доброго времени суток!

Хочу рассказать о том как можно, используя FUSE написать программу-клиент для Яндекс.Диск и подобных сервисов. У программы будет несложный, но симпатишный GUI.



Что нам понадобиться




При приготовлении программы будем использоваться следующие ингредиенты:


  1. С++

  2. Qt (4.x)

  3. curl

  4. libxml

  5. FUSE (Dokan для Windows)

  6. Яндекс.Диск API




Все находится в открытом доступе. Думаю у вас не возникнет проблем все это найти и скачать.

По задумке, программа должна быть легкой в использовании. Должна работать как на Linux, так и на Windows. Плюс хочется, чтобы можно было относительно несложно расширять функционал программы, посредством подключения других сервисов. Как то: ВКонтакте, Google.Docs и т.п.

Архитектура




Программа будет состоять из следующих крупных блоков:


  • UI

  • FUSE или Dokan (выбрать по вкусу)

  • Local Driver

  • Remote Driver

  • Connector

  • И часть которая, все это будет соединять, назовем ее Common




Диаграмма с основными блоками и их составными частями будет выглядеть следующим образом:

image


UI



Тут я думаю ничего интересного. Обычный Qt. Диалог с настройками выглядит таким образом:


FUSE



Используя драйвер файловой системы мы сможем отслеживать все операции над нужными нам файлами и уведомлять сторонние сервисы обо всех изменениях. Например, мы можем сохранить фотографию на нашем виртуальном диске. Потом открыть ее, например, в Gimp и отредактировать. Далее сохранить изменения прямо в Gimp и эти изменения автоматически попадут в облако Яндекс.Диска. FUSE уведомит нас, что определенная фотография изменилась и мы сможем отправить эти изменения в облако. На самом деле отслеживать изменения файлов можно и другими способами, но вариант с FUSE показался самым интересным. Хотя стоит признать, что он и очень сложный. Например, в той же Windows легко получить синий экран смерти, если некорректно обработать вызов Dokan'а.
Local Driver



Это фактически обертка для драйвера файловой системы в пользовательском пространстве. Напомню, в нашем случае этими драйверами являются FUSE или Dokan, в зависимости от операционной системы. В задачу этой самой обертки входит реакция на вызовы драйвера файловой системы и проброска их уже в Common часть. Обертка нам нужна для того, чтобы можно было подменять бэкенд в лице FUSE на Dokan и обратно, и при этом ничего не менять в остальной части программы. Для Fuse нам нужно обработать следующие вызовы драйвера файловой системы:

static int fuseGetAttr(const char *path, struct stat *statbuf);
static int fuseReadLink(const char *path, char *link, size_t size);
static int fuseMknod(const char *path, mode_t mode, dev_t dev);
static int fuseMkdir(const char *path, mode_t mode);
static int fuseUnlink(const char *path);
static int fuseRmdir(const char *path);
static int fuseSymlink(const char *path, const char *link);
static int fuseRename(const char *path, const char *newpath);
static int fuseLink(const char *path, const char *newpath);
static int fuseChmod(const char *path, mode_t mode);
static int fuseChown(const char *path, uid_t uid, gid_t gid);
static int fuseTruncate(const char *path, off_t newSize);
static int fuseUtime(const char *path, struct utimbuf *ubuf);
static int fuseOpen(const char *path, struct fuse_file_info *fileInfo);
static int fuseRead(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fileInfo);
static int fuseWrite(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fileInfo);
static int fuseStatfs(const char *path, struct statvfs *statInfo);
static int fuseFlush(const char *path, struct fuse_file_info *fileInfo);
static int fuseRelease(const char *path, struct fuse_file_info *fileInfo);
static int fuseFsync(const char *path, int datasync, struct fuse_file_info *fi);
static int fuseSetxAttr(const char *path, const char *name, const char *value, size_t size, int flags);
static int fuseGetxAttr(const char *path, const char *name, char *value, size_t size);
static int fuseListxAttr(const char *path, char *list, size_t size);
static int fuseRemovexAttr(const char *path, const char *name);
static int fuseOpenDir(const char *path, struct fuse_file_info *fileInfo);
static int fuseReadDir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fileInfo);
static int fuseReleaseDir(const char *path, struct fuse_file_info *fileInfo);
static int fuseFsyncDir(const char *path, int datasync, struct fuse_file_info *fileInfo);
static void* fuseInit(struct fuse_conn_info *conn);
static int fuseUtimens(const char *path, const struct timespec ts[2]);


Реализацию я приводить здесь не буду. Ее можно посмотреть в репозитории проекта, в файле linux_lvfs_driver.cpp

Аналог для Windows лежит здесь


Remote Driver



Тут надо сделать небольшое отступление. Как я уже писал выше, одним из требований к программе было возможность подключать различные сторонние сервисы. Для этого будем использовать систему плагинов, реализованную с использованием Qt. Тут тоже не будет каких-то откровений, при желании про плагины найдете много инфы в интернетах.

Дык вот Remote Driver — это абстракция для управления неким абстрактным плагином. Через Remote Driver будет происходить общение Common части нашей программы с конкретным плагином, реализующим работу со сторонним сервисом.


Connector



Connector является еще одной очень важной частью нашей программы. В задачу Connector'a входит абстрагировать работу c API различных сервисов. Будь то API Яндекс.Диск, Яндекс.Фоток или ВКонтакте. Приведу здесь объявление класса коннектора для Яндекс.Диск:

class YaDiskHTTPConnector : public QObject
{
Q_OBJECT
public:
YaDiskHTTPConnector();
~YaDiskHTTPConnector();

void setSettings(const QString& login
, const QString& password
, const QString& proxy
, const QString& proxyLoginPwd
, bool isOAuth
, const QString& token);
RESULT getTreeElements(const QString& path, QString& response);
RESULT downloadFile(const QString& url, const QString& path);
RESULT downloadFiles(const QList <QString>& urlList, const QList <QString>& pathList);
RESULT uploadFile(const QString& path, const QString& title, const QString& parentId, QString& response);
RESULT deleteFile(const QString& path, QString& response);
RESULT createDirectory(const QString& title, const QString& parentId, QString& response);
RESULT moveElement(const QString& id, const QString& oldParentId, const QString& newParentId, ElementType type, QString& response);
RESULT renameElement(const QString& id, ElementType type, const QString& newTitle, QString& response);
void setToken(const QString& token);
private:
static size_t writeStr(void *ptr, size_t size, size_t count, void *response);
static size_t fwrite_b(void *ptr, size_t size, size_t count, void *path);
static size_t readStr(void *ptr, size_t size, size_t nmemb, void *stream);
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream);
int execQuery(const QString &url, const QString &header, const QString &postFields, QString* response);
private:
struct sPutData
{
const char* m_data;
size_t m_len;
};
private:
QString m_login;
QString m_password;
QString m_proxy;
QString m_proxyLoginPwd;
bool m_isOAuth;

QString m_token;
QString m_requestId;
QString m_key;
QMutex m_connectorMutex;
};


На самом деле похожий вид имеет любой другой коннектор к стороннему сервису. На данный момент реализованы коннекторы для следующих сервисов:



  • Яндекс.Диск

  • Яндекс.Фотки

  • Facebook (работа с фото)

  • Вконтакте(работа с фото)

  • Google.Docs




Реализацию коннектора для Яндекс.Диск можно найти в этом файле. Для отсылки запросов к сервису используется всем известный CURL.

Ну вот и все




Объединив все это вместе, получим наш простенький клиент, работающий под двумя операционными системами и с различными облачными сервисами.

Полную версию исходников проекта можно найти ТУТ.

Если вы хотите помочь в развитии программы — добро пожаловать!


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий