Good day to all!
I want to talk about how you can, using
FUSE, write a client program for Yandex.Disk and similar services. The program will have a simple, but simpatishny GUI.
What do we need
When preparing the program we will use the following ingredients:
- C ++
- Qt (4.x)
- curl
- libxml
- FUSE ( Dokan for Windows)
- Yandex.Disk API
Everything is in the public domain. I think you will not have problems to find and download all this.
As planned, the program should be easy to use. It should work on both Linux and Windows. Plus, I want it to be relatively easy to expand the functionality of the program by connecting other services. Like this: VKontakte, Google.Docs, etc.
Architecture
The program will consist of the following large blocks:
- Ui
- FUSE or Dokan (choose to taste)
- Local driver
- Remote driver
- Connector
- And the part that will connect all this, let's call it Common
The diagram with the main blocks and their components will look as follows:
')

Ui
Here I think nothing interesting. Normal Qt. The dialog with the settings looks like this:

FUSE
Using the file system driver, we will be able to track all operations on the files we need and notify third-party services about all changes. For example, we can save a photo on our virtual disk. Then open it, for example, in Gimp and edit. Then save the changes directly to Gimp and these changes will automatically fall into the Yandex.Disk cloud. FUSE will notify us that a particular photo has changed and we can send these changes to the cloud. In fact, you can track file changes in other ways, but the FUSE version seemed to be the most interesting. Although it is recognized that it is very difficult. For example, in the same Windows it is easy to get the blue screen of death, if you do not handle the Dokan call correctly.
Local driver
This is actually a wrapper for the file system driver in user space. Let me remind you that in our case these drivers are FUSE or Dokan, depending on the operating system. The task of the wrapper itself is to react to the calls of the file system driver and forward them to the Common Part. We need a wrapper in order to be able to replace the back end in the face of FUSE with Dokan and back, and at the same time do not change anything in the rest of the program. For Fuse, we need to handle the following calls to the file system driver:
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]);
I will not give the implementation here. You can see it in the project repository, in the file
linux_lvfs_driver.cppAnalog for Windows is
hereRemote driver
Here we must make a small digression. As I wrote above, one of the requirements for the program was the ability to connect various third-party services. For this we will use the plugin system implemented using Qt. Here, too, there will not be any revelations, if desired, about plug-ins will find a lot of information on the Internet.
So here is the Remote Driver - an abstraction to manage some abstract plugin. Through the Remote Driver, the Common part of our program will communicate with a specific plugin that implements work with a third-party service.
Connector
Connector is another very important part of our program. The task of the Connector is to abstract the work of the API of various services. Whether it is Yandex.Disk API, Yandex.Fotok or VKontakte. I will give here the declaration of the class connector for Yandex.Disk:
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; };
In fact, any other connector to a third-party service has a similar appearance. Currently, connectors for the following services are implemented:
- Yandex.Disk
- Yandex.Fotki
- Facebook (photo work)
- Vkontakte (work with photos)
- Google.Docs
The implementation of the connector for Yandex.Disk can be found in this
file . To send requests to the service used by all known CURL.
Well that's all
Combining all this together, we get our simple client, working under two operating systems and with various cloud services.
The full version of the source code of the project can be found
HERE .
If you want to help in the development of the program - welcome!
PS Link to the
installer for Windows. Under Linux, compiling is not possible at this time. I'll post it a little later.