Пишем простой аналог клиента Яндекс.Диск, но под 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.

Ну вот и все

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

Полную версию исходников проекта можно найти ТУТ.
Если вы хотите помочь в развитии программы — добро пожаловать!

ссылка на оригинал статьи http://habrahabr.ru/post/196370/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *