1 /*
2  * Copyright (C) by Christian Kamm <mail@ckamm.de>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12  * for more details.
13  */
14 #pragma once
15 
16 #include <QObject>
17 #include <QScopedPointer>
18 #include <QSharedPointer>
19 
20 #include <memory>
21 
22 #include "assert.h"
23 #include "ocsynclib.h"
24 #include "result.h"
25 #include "syncfilestatus.h"
26 #include "pinstate.h"
27 
28 typedef struct csync_file_stat_s csync_file_stat_t;
29 
30 namespace OCC {
31 
32 class Account;
33 typedef QSharedPointer<Account> AccountPtr;
34 class SyncJournalDb;
35 class VfsPrivate;
36 class SyncFileItem;
37 
38 /** Collection of parameters for initializing a Vfs instance. */
39 struct OCSYNC_EXPORT VfsSetupParams
40 {
41     /** The full path to the folder on the local filesystem
42      *
43      * Always ends with /.
44      */
45     QString filesystemPath;
46 
47     /** The path to the synced folder on the account
48      *
49      * Always ends with /.
50      */
51     QString remotePath;
52 
53     /// Account url, credentials etc for network calls
54     AccountPtr account;
55 
56     /** Access to the sync folder's database.
57      *
58      * Note: The journal must live at least until the Vfs::stop() call.
59      */
60     SyncJournalDb *journal = nullptr;
61 
62     /// Strings potentially passed on to the platform
63     QString providerName;
64     QString providerVersion;
65 
66     /** when registering with the system we might use
67      *  a different presentaton to identify the accounts
68      */
69     bool multipleAccountsRegistered = false;
70 };
71 
72 /** Interface describing how to deal with virtual/placeholder files.
73  *
74  * There are different ways of representing files locally that will only
75  * be filled with data (hydrated) on demand. One such way would be suffixed
76  * files, others could be FUSE based or use Windows CfAPI.
77  *
78  * This interface intends to decouple the sync algorithm and Folder from
79  * the details of how a particular VFS solution works.
80  *
81  * An instance is usually created through a plugin via the createVfsFromPlugin()
82  * function.
83  */
84 class OCSYNC_EXPORT Vfs : public QObject
85 {
86     Q_OBJECT
87 
88 public:
89     /** The kind of VFS in use (or no-VFS)
90      *
91      * Currently plugins and modes are one-to-one but that's not required.
92      */
93     enum Mode
94     {
95         Off,
96         WithSuffix,
97         WindowsCfApi,
98     };
99     Q_ENUM(Mode)
100     static QString modeToString(Mode mode);
101     static Optional<Mode> modeFromString(const QString &str);
102 
103     static Result<bool, QString> checkAvailability(const QString &path);
104 
105     enum class AvailabilityError
106     {
107         // Availability can't be retrieved due to db error
108         DbError,
109         // Availability not available since the item doesn't exist
110         NoSuchItem,
111     };
112     using AvailabilityResult = Result<VfsItemAvailability, AvailabilityError>;
113 
114 public:
115     explicit Vfs(QObject* parent = nullptr);
116     ~Vfs() override;
117 
118     virtual Mode mode() const = 0;
119 
120     /// For WithSuffix modes: the suffix (including the dot)
121     virtual QString fileSuffix() const = 0;
122 
123     /// Access to the parameters the instance was start()ed with.
params()124     const VfsSetupParams &params() const { return _setupParams; }
125 
126 
127     /** Initializes interaction with the VFS provider.
128      *
129      * The plugin-specific work is done in startImpl().
130      */
131     void start(const VfsSetupParams &params);
132 
133     /// Stop interaction with VFS provider. Like when the client application quits.
134     virtual void stop() = 0;
135 
136     /// Deregister the folder with the sync provider, like when a folder is removed.
137     virtual void unregisterFolder() = 0;
138 
139 
140     /** Whether the socket api should show pin state options
141      *
142      * Some plugins might provide alternate shell integration, making the normal
143      * context menu actions redundant.
144      */
145     virtual bool socketApiPinStateActionsShown() const = 0;
146 
147     /** Return true when download of a file's data is currently ongoing.
148      *
149      * See also the beginHydrating() and doneHydrating() signals.
150      */
151     virtual bool isHydrating() const = 0;
152 
153     /** Update placeholder metadata during discovery.
154      *
155      * If the remote metadata changes, the local placeholder's metadata should possibly
156      * change as well.
157      */
158     virtual OC_REQUIRED_RESULT Result<void, QString> updateMetadata(const QString &filePath, time_t modtime, qint64 size, const QByteArray &fileId) = 0;
159 
160     /// Create a new dehydrated placeholder. Called from PropagateDownload.
161     virtual OC_REQUIRED_RESULT Result<void, QString> createPlaceholder(const SyncFileItem &item) = 0;
162 
163     /** Convert a hydrated placeholder to a dehydrated one. Called from PropagateDownlaod.
164      *
165      * This is different from delete+create because preserving some file metadata
166      * (like pin states) may be essential for some vfs plugins.
167      */
168     virtual OC_REQUIRED_RESULT Result<void, QString> dehydratePlaceholder(const SyncFileItem &item) = 0;
169 
170     /** Discovery hook: even unchanged files may need UPDATE_METADATA.
171      *
172      * For instance cfapi vfs wants local hydrated non-placeholder files to
173      * become hydrated placeholder files.
174      */
175     virtual OC_REQUIRED_RESULT bool needsMetadataUpdate(const SyncFileItem &item) = 0;
176 
177     /** Convert a new file to a hydrated placeholder.
178      *
179      * Some VFS integrations expect that every file, including those that have all
180      * the remote data, are "placeholders". This function is called by PropagateDownload
181      * to convert newly downloaded, fully hydrated files into placeholders.
182      *
183      * Implementations must make sure that calling this function on a file that already
184      * is a placeholder is acceptable.
185      *
186      * replacesFile can optionally contain a filesystem path to a placeholder that this
187      * new placeholder shall supersede, for rename-replace actions with new downloads,
188      * for example.
189      */
190     virtual OC_REQUIRED_RESULT Result<void, QString> convertToPlaceholder(
191         const QString &filename,
192         const SyncFileItem &item,
193         const QString &replacesFile = QString()) = 0;
194 
195     /// Determine whether the file at the given absolute path is a dehydrated placeholder.
196     virtual OC_REQUIRED_RESULT bool isDehydratedPlaceholder(const QString &filePath) = 0;
197 
198     /** Similar to isDehydratedPlaceholder() but used from sync discovery.
199      *
200      * This function shall set stat->type if appropriate.
201      * It may rely on stat->path and stat_data (platform specific data).
202      *
203      * Returning true means that type was fully determined.
204      */
205     virtual OC_REQUIRED_RESULT bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0;
206 
207     /** Sets the pin state for the item at a path.
208      *
209      * The pin state is set on the item and for all items below it.
210      *
211      * Usually this would forward to setting the pin state flag in the db table,
212      * but some vfs plugins will store the pin state in file attributes instead.
213      *
214      * folderPath is relative to the sync folder. Can be "" for root folder.
215      */
216     virtual OC_REQUIRED_RESULT bool setPinState(const QString &folderPath, PinState state) = 0;
217 
218     /** Returns the pin state of an item at a path.
219      *
220      * Usually backed by the db's effectivePinState() function but some vfs
221      * plugins will override it to retrieve the state from elsewhere.
222      *
223      * folderPath is relative to the sync folder. Can be "" for root folder.
224      *
225      * Returns none on retrieval error.
226      */
227     virtual OC_REQUIRED_RESULT Optional<PinState> pinState(const QString &folderPath) = 0;
228 
229     /** Returns availability status of an item at a path.
230      *
231      * The availability is a condensed user-facing version of PinState. See
232      * VfsItemAvailability for details.
233      *
234      * folderPath is relative to the sync folder. Can be "" for root folder.
235      */
236     virtual OC_REQUIRED_RESULT AvailabilityResult availability(const QString &folderPath) = 0;
237 
238 public slots:
239     /** Update in-sync state based on SyncFileStatusTracker signal.
240      *
241      * For some vfs plugins the icons aren't based on SocketAPI but rather on data shared
242      * via the vfs plugin. The connection to SyncFileStatusTracker allows both to be based
243      * on the same data.
244      */
245     virtual void fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus) = 0;
246 
247 signals:
248     /// Emitted when a user-initiated hydration starts
249     void beginHydrating();
250     /// Emitted when the hydration ends
251     void doneHydrating();
252 
253 protected:
254     /** Setup the plugin for the folder.
255      *
256      * For example, the VFS provider might monitor files to be able to start a file
257      * hydration (download of a file's remote contents) when the user wants to open
258      * it.
259      *
260      * Usually some registration needs to be done with the backend. This function
261      * should take care of it if necessary.
262      */
263     virtual void startImpl(const VfsSetupParams &params) = 0;
264 
265     // Db-backed pin state handling. Derived classes may use it to implement pin states.
266     bool setPinStateInDb(const QString &folderPath, PinState state);
267     Optional<PinState> pinStateInDb(const QString &folderPath);
268     AvailabilityResult availabilityInDb(const QString &folderPath);
269 
270     // the parameters passed to start()
271     VfsSetupParams _setupParams;
272 };
273 
274 /// Implementation of Vfs for Vfs::Off mode - does nothing
275 class OCSYNC_EXPORT VfsOff : public Vfs
276 {
277     Q_OBJECT
278 
279 public:
280     VfsOff(QObject* parent = nullptr);
281     ~VfsOff() override;
282 
mode()283     Mode mode() const override { return Vfs::Off; }
284 
fileSuffix()285     QString fileSuffix() const override { return QString(); }
286 
stop()287     void stop() override {}
unregisterFolder()288     void unregisterFolder() override {}
289 
socketApiPinStateActionsShown()290     bool socketApiPinStateActionsShown() const override { return false; }
isHydrating()291     bool isHydrating() const override { return false; }
292 
updateMetadata(const QString &,time_t,qint64,const QByteArray &)293     Result<void, QString> updateMetadata(const QString &, time_t, qint64, const QByteArray &) override { return {}; }
createPlaceholder(const SyncFileItem &)294     Result<void, QString> createPlaceholder(const SyncFileItem &) override { return {}; }
dehydratePlaceholder(const SyncFileItem &)295     Result<void, QString> dehydratePlaceholder(const SyncFileItem &) override { return {}; }
convertToPlaceholder(const QString &,const SyncFileItem &,const QString &)296     Result<void, QString> convertToPlaceholder(const QString &, const SyncFileItem &, const QString &) override { return {}; }
297 
needsMetadataUpdate(const SyncFileItem &)298     bool needsMetadataUpdate(const SyncFileItem &) override { return false; }
isDehydratedPlaceholder(const QString &)299     bool isDehydratedPlaceholder(const QString &) override { return false; }
statTypeVirtualFile(csync_file_stat_t *,void *)300     bool statTypeVirtualFile(csync_file_stat_t *, void *) override { return false; }
301 
setPinState(const QString &,PinState)302     bool setPinState(const QString &, PinState) override { return true; }
pinState(const QString &)303     Optional<PinState> pinState(const QString &) override { return PinState::AlwaysLocal; }
availability(const QString &)304     AvailabilityResult availability(const QString &) override { return VfsItemAvailability::AlwaysLocal; }
305 
306 public slots:
fileStatusChanged(const QString &,SyncFileStatus)307     void fileStatusChanged(const QString &, SyncFileStatus) override {}
308 
309 protected:
startImpl(const VfsSetupParams &)310     void startImpl(const VfsSetupParams &) override {}
311 };
312 
313 /// Check whether the plugin for the mode is available.
314 OCSYNC_EXPORT bool isVfsPluginAvailable(Vfs::Mode mode);
315 
316 /// Return the best available VFS mode.
317 OCSYNC_EXPORT Vfs::Mode bestAvailableVfsMode();
318 
319 /// Create a VFS instance for the mode, returns nullptr on failure.
320 OCSYNC_EXPORT std::unique_ptr<Vfs> createVfsFromPlugin(Vfs::Mode mode);
321 
322 } // namespace OCC
323