Pull to refresh

Пишем простой аналог клиента Яндекс.Диск под Linux (и Windows)

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

Хочу рассказать о том как можно, используя 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.

Ну вот и все


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

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

P.S. Ссылка на инсталлятор для Windows. Под Linux скомпилировать сейчас нет возможности. Чуть позже выложу.
Tags:
Hubs:
Total votes 49: ↑42 and ↓7+35
Comments10

Articles