001package co.codewizards.cloudstore.core.repo.transport;
002
003import java.net.URL;
004import java.util.Date;
005import java.util.UUID;
006
007import co.codewizards.cloudstore.core.dto.ChangeSetDto;
008import co.codewizards.cloudstore.core.dto.ConfigPropSetDto;
009import co.codewizards.cloudstore.core.dto.DirectoryDto;
010import co.codewizards.cloudstore.core.dto.NormalFileDto;
011import co.codewizards.cloudstore.core.dto.RepoFileDto;
012import co.codewizards.cloudstore.core.dto.RepositoryDto;
013import co.codewizards.cloudstore.core.dto.SymlinkDto;
014import co.codewizards.cloudstore.core.dto.VersionInfoDto;
015import co.codewizards.cloudstore.core.oio.File;
016import co.codewizards.cloudstore.core.util.IOUtil;
017
018/**
019 * Transport abstraction.
020 * <p>
021 * The naming in this interface assumes a local client talking to a remote repository. But the
022 * repository accessed via this transport does not need to be remote - it might be in the local
023 * file system!
024 * <p>
025 * More precisely:
026 * <p>
027 * "Remote repository" references the repository which is accessed via this transport layer. The word
028 * "remote" thus indicates that there <b>might</b> be some distance between here and wherever this repository
029 * is located.
030 * <p>
031 * "Client" should primarily be understood as <i>API client</i>, i.e. the code using the methods of this
032 * interface. The "client repository" is the repository for which some client code accesses this
033 * {@code RepoTransport}, therefore the "client repository" is used for repo-to-repo-authentication. Some
034 * methods in this interface can be used without authentication (i.e. anonymously) - therefore a "client
035 * repository" is optional.
036 * <p>
037 * The synchronisation logic accesses all repositories through this abstraction layer. Therefore,
038 * the synchronisation logic does not need to know any details about how to communicate with
039 * a repository.
040 * <p>
041 * There are currently two implementations:
042 * <ul>
043 * <li>file-system-based (for local repositories)
044 * <li>REST-based (for remote repositories)
045 * </ul>
046 * Further implementations might be written later.
047 * <p>
048 * An instance of an implementation of {@code RepoTransport} is obtained via the
049 * {@link RepoTransportFactory}.
050 * <p>
051 * <b>Important:</b> Implementors should <i>not</i> directly implement this interface, but instead sub-class
052 * {@link AbstractRepoTransport}!
053 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
054 */
055public interface RepoTransport extends AutoCloseable {
056
057        /**
058         * Gets the factory which created this instance.
059         * @return the factory which created this instance. Should never be <code>null</code>, if properly initialised.
060         * @see #setRepoTransportFactory(RepoTransportFactory)
061         */
062        RepoTransportFactory getRepoTransportFactory();
063        /**
064         * Sets the factory which created this instance.
065         * @param repoTransportFactory the factory which created this instance. Must not be <code>null</code>.
066         * @see #getRepoTransportFactory()
067         */
068        void setRepoTransportFactory(RepoTransportFactory repoTransportFactory);
069
070        /**
071         * Gets the remote repository's root URL, maybe including a {@linkplain #getPathPrefix() path-prefix}.
072         * <p>
073         * This is thus the remote repository's root URL as used to synchronise a certain local repository.
074         * The word "remote" should not be misunderstood as actually on another computer. It just means behind
075         * this transport abstraction.
076         * <p>
077         * In contrast to the {@link #getRemoteRootWithoutPathPrefix() remoteRootWithoutPathPrefix}, this is
078         * the connection point for the synchronisation, which might be a sub-directory, i.e. not the native
079         * root of the connected repository.
080         * @return the remote repository's root URL, maybe including a {@linkplain #getPathPrefix() path-prefix}.
081         * Never <code>null</code>, if properly initialised.
082         * @see #setRemoteRoot(URL)
083         */
084        URL getRemoteRoot();
085        /**
086         * Sets the remote repository's root URL.
087         * <p>
088         * This URL is the point where the {@linkplain #getClientRepositoryId() client-repository} is connected
089         * to the repository managed by this {@code RepoTransport}.
090         * <p>
091         * You should never directly invoke this method! It is automatically called when creating a
092         * {@code RepoTransport} instance via the {@link RepoTransportFactory}.
093         * <p>
094         * Invoking this method twice with different {@code remoteRoot} values is not allowed. The {@code remoteRoot}
095         * cannot be changed after it was once set.
096         * @param remoteRoot the remote repository's root URL. It may be <code>null</code>, but this
097         * {@code RepoTransport} is only usable, after this method was invoked with a non-<code>null</code> value.
098         * @see #getRemoteRoot()
099         */
100        void setRemoteRoot(URL remoteRoot);
101
102        /**
103         * Gets the client repository's unique identifier.
104         * <p>
105         * The word "client" does not necessarily mean a remote JVM. It merely means the API client accessing
106         * this {@code RepoTransport} API.
107         * <p>
108         * This property might be <code>null</code>. If it is not set, only operations which do not require
109         * repo-to-repo-authentication can be used.
110         * @return the client repository's identifier. Might be <code>null</code>.
111         * @see #setClientRepositoryId(UUID)
112         */
113        UUID getClientRepositoryId();
114        /**
115         * Sets the client's repository identifier.
116         * @param clientRepositoryId the client's repository identifier. May be <code>null</code>.
117         * @see #getClientRepositoryId()
118         */
119        void setClientRepositoryId(UUID clientRepositoryId);
120
121        /**
122         * Gets the remote repository's root URL without the {@linkplain #getPathPrefix() path-prefix}.
123         * <p>
124         * In other words, this is the repository's <b>native root</b>, even if the connection is established to a
125         * sub-directory.
126         * @return the remote repository's root URL without the {@linkplain #getPathPrefix() path-prefix}. Never
127         * <code>null</code>, if properly initialised.
128         */
129        URL getRemoteRootWithoutPathPrefix();
130
131        /**
132         * Prefix for every path (as used in {@link #delete(String)} for example).
133         * <p>
134         * It is possible to connect to a repository at a sub-directory, i.e. not the repo's root. If this
135         * {@code RepoTransport} is connected to the repo's root, this {@code pathPrefix} is an empty string.
136         * But if this {@code RepoTransport} is connected to a sub-directory, this sub-directory will be the
137         * {@code pathPrefix}.
138         * <p>
139         * For example, if the {@link #getRemoteRoot() remoteRoot} is
140         * <code>"https://some.host/some/repo/Private+pictures/Wedding+%26+honeymoon"</code> and the
141         * {@link #getRemoteRootWithoutPathPrefix() remoteRootWithoutPathPrefix} is
142         * <code>"https://some.host/some/repo"</code>,
143         * then this {@code pathPrefix} will be <code>"/Private pictures/Wedding &amp; honeymoon"</code>.
144         * <p>
145         * As shown in this example, the {@code pathPrefix} is - just like every other path - <b>not</b> encoded
146         * in any way! The separator for the path-segments inside this prefix is "/" on all operating systems.
147         * <p>
148         * The {@code RepoTransport} implementations use this prefix to calculate back and forth between the
149         * path relative to the connected {@code remoteRoot} and the complete path used in the repository.
150         */
151        String getPathPrefix();
152
153        /**
154         * Prepend the {@link #getPathPrefix() pathPrefix} to the given {@code path}.
155         * @param path the path to be prepended. Must not be <code>null</code>.
156         * @return the complete path composed of the {@link #getPathPrefix() pathPrefix} and the given
157         * {@code path}. Never <code>null</code>.
158         * @see #unprefixPath(String)
159         * @see #getPathPrefix()
160         */
161        String prefixPath(String path);
162
163        /**
164         * Cut the {@link #getPathPrefix() pathPrefix} from the given {@code path}.
165         * @param path the path to be shortened. Must not be <code>null</code>. Of course, this path
166         * must start with {@link #getPathPrefix() pathPrefix}.
167         * @return the new shortened path without the {@link #getPathPrefix() pathPrefix}. Never
168         * <code>null</code>.
169         * @see #prefixPath(String)
170         * @see #getPathPrefix()
171         */
172        String unprefixPath(String path);
173
174        /**
175         * Gets the remote repository's repository-descriptor.
176         * <p>
177         * This operation does not require authentication! It can (and is regularly) invoked anonymously.
178         * @return the remote repository's repository-descriptor. Never <code>null</code>.
179         */
180        RepositoryDto getRepositoryDto();
181
182        /**
183         * Get the remote repository's unique identifier.
184         * @return the repository's unique identifier.
185         */
186        UUID getRepositoryId();
187
188        /**
189         * Gets the remote repository's public key.
190         * @return the remote repository's public key. Never <code>null</code>.
191         */
192        byte[] getPublicKey();
193
194        /**
195         * Request to connect the {@linkplain #getClientRepositoryId() client repository} with
196         * {@linkplain #getRepositoryId() the remote repository}.
197         * @param publicKey the public key of the client repository which requests the connection. Must not be
198         * <code>null</code>.
199         */
200        void requestRepoConnection(byte[] publicKey);
201
202        /**
203         * Gets the change-set from the remote repository.
204         * <p>
205         * The invocation of this method marks the beginning of a synchronisation. After the synchronisation is
206         * complete, the {@link #endSyncFromRepository()} method must be invoked to notify the remote repository
207         * that all changes contained in the change set have been successfully and completely written to the
208         * client repository.
209         * <p>
210         * The change-set is dependent on the client repository: Every client repository gets its own individual
211         * change-set. The remote repository tracks which changes need to be sent to the client. In normal
212         * operation, the same change is transferred only once. Under certain circumstances, however, the same
213         * change might be transferred multiple times and the client must cope with this! Such duplicate
214         * transfers happen, if the transfer is interrupted - i.e. the {@link #endSyncFromRepository()} was not
215         * invoked.
216         * <p>
217         * Please note that the DTOs in this {@link ChangeSetDto} do not need to be completely resolved. They
218         * might be incomplete in order to reduce the size of the {@link ChangeSetDto}. For example,
219         * {@link NormalFileDto#getFileChunkDtos() NormalFileDto.fileChunkDtos} is not populated. These details
220         * are separately requested, later - e.g. by {@link #getRepoFileDto(String)}.
221         * @param localSync <code>true</code> indicates that the remote repository should perform a local sync
222         * before calculating the change set. <code>false</code> indicates that the remote repository should
223         * abstain from a local sync. This flag is a hint and the remote repository does not need to adhere it.
224         * @param lastSyncToRemoteRepoLocalRepositoryRevisionSynced the last revision that was synced.
225         * May be <code>null</code>. If it is <i>not</i> <code>null</code>, the property
226         * {@code co.codewizards.cloudstore.local.persistence.LastSyncToRemoteRepo.localRepositoryRevisionSynced}
227         * is overwritten by this value, before querying the changes. This causes all data modified in greater
228         * (not equal) revisions to be included.
229         * @return the change-set from the remote repository. Never <code>null</code>.
230         */
231        ChangeSetDto getChangeSetDto(boolean localSync, Long lastSyncToRemoteRepoLocalRepositoryRevisionSynced);
232
233        /**
234         * Notifies the destination repository that this change-set is about to be synced into it.
235         * @param changeSetDto the change-set from the other RepoTransport (the source of the sync). Never <code>null</code>.
236         */
237        void prepareForChangeSetDto(ChangeSetDto changeSetDto);
238
239        /**
240         * Creates the specified directory (including all parent-directories as needed).
241         * <p>
242         * If the directory already exists, this is a noop.
243         * <p>
244         * If there is any obstruction in the way of this path (e.g. a normal file), it is moved away (renamed or simply deleted
245         * depending on the conflict resolution strategy).
246         * @param path the path of the directory. Must not be <code>null</code>. No matter which operating system is used,
247         * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
248         * It is always relative to the repository's root directory.
249         * @param lastModified the {@linkplain IOUtil#getLastModifiedNoFollow(File) last-modified-timestamp} the newly created
250         * directory will be set to.
251         * May be <code>null</code> (in which case the {@code lastModified} property is not touched). This applies only to the
252         * actual directory and not to the parent-directories! The parent-directories' {@code lastModified} properties are never
253         * touched - even if the parent-directories are newly created.
254         */
255        void makeDirectory(String path, Date lastModified);
256
257        void makeSymlink(String path, String target, Date lastModified);
258
259        void copy(String fromPath, String toPath);
260        void move(String fromPath, String toPath);
261
262        /**
263         * Deletes the file (or directory) specified by {@code path}.
264         * <p>
265         * If there is no such file (or directory), this method is a noop.
266         * <p>
267         * If {@code path} denotes a directory, all its children (if there are) are deleted recursively.
268         * @param path the path of the file (or directory) to be deleted. Must not be <code>null</code>. No matter which
269         * operating system is used, the separation-character is always '/'. This path may start with a "/", but there is no
270         * difference, if it does: It is always relative to the repository's root directory.
271         */
272        void delete(String path);
273
274        /**
275         * Gets the data of the {@linkplain NormalFileDto file} (or {@linkplain DirectoryDto directory} or
276         * {@linkplain SymlinkDto symlink}) identified by the given {@code path}.
277         * @param path the path to the file.
278         * @return the data of the file referenced by {@code path}. Never <code>null</code>.
279         */
280        RepoFileDto getRepoFileDto(String path);
281
282        /**
283         * Get the binary file data at the given {@code offset} and with the given {@code length}.
284         * <p>
285         * If the file was modified/deleted, this method should not fail, but simply return <code>null</code>
286         * or a result being shorter than the {@code length} specified.
287         * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
288         * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
289         * It is always relative to the repository's root directory.
290         * @param offset the offset of the first byte to be read (0-based).
291         * @param length the length of the data to be read. -1 to read from {@code offset} to the end of the file.
292         */
293        byte[] getFileData(String path, long offset, int length);
294
295        /**
296         * Begins a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it).
297         * <p>
298         * Usually, this method creates the specified file in the file system (if necessary with parent-directories)
299         * and in the database. But this operation may be deferred until {@link #endPutFile(String, Date, long, String)}.
300         * <p>
301         * If the file is immediately created, it should not be synchronised to any other repository, yet! It should
302         * be ignored, until {@link #endPutFile(String, Date, long, String)} was called for it.
303         * <p>
304         * In normal operation, zero or more invocations of {@link #putFileData(String, long, byte[])} and
305         * finally one invocation of {@link #endPutFile(String, Date, long, String)} follow this method. However, this is not
306         * guaranteed and the file transfer may be interrupted. If it is resumed, later this method is called again,
307         * without {@link #endPutFile(String, Date, long, String)} ever having been called in between.
308         * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
309         * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
310         * It is always relative to the repository's root directory.
311         * @see #putFileData(String, long, byte[])
312         * @see #endPutFile(String, Date, long, String)
313         */
314        void beginPutFile(String path);
315
316        /**
317         * Write a block of binary data into the file.
318         * <p>
319         * This method may only be called after {@link #beginPutFile(String)} and before {@link #endPutFile(String, Date, long, String)}.
320         * @param offset the 0-based position in the file at which the block should be written.
321         * @see #beginPutFile(String)
322         * @see #endPutFile(String, Date, long, String)
323         */
324        void putFileData(String path, long offset, byte[] fileData);
325
326        /**
327         * Ends a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it).
328         * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
329         * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
330         * It is always relative to the repository's root directory.
331         * @param lastModified when was the file's last modification. Must not be <code>null</code>.
332         * @param length the length of the file in bytes. If the file already existed and was longer, it is
333         * truncated to this length.
334         * @param sha1 the SHA1 hash of the file. May be <code>null</code>. If it is given, the repository may
335         * log a warning, if the current file is different. It should not throw an exception, because it
336         * is a valid state that a file is modified (by another process) while it is transferred.
337         */
338        void endPutFile(String path, Date lastModified, long length, String sha1);
339
340        /**
341         * Marks the end of a synchronisation <b>from</b> the remote repository behind this {@code RepoTransport}.
342         * <p>
343         * This method should be invoked after all changes indicated by {@link #getChangeSetDto(boolean, long)} have
344         * been completely written into the client repository.
345         * <p>
346         * After this method was invoked, {@link #getChangeSetDto(boolean, long)} will return the new changes only.
347         * New changes means all those changes that were accumulated after its last invocation - not after the
348         * invocation of this method! This method might be called some time after {@code getChangeSetDto(...)}
349         * and it must be guaranteed that changes done between {@code getChangeSetDto(...)} and
350         * {@code endSyncFromRepository()} are contained in the next invocation of {@code getChangeSetDto(...)}.
351         * <p>
352         * This method must not be invoked, if an error was encountered during the synchronisation! It must thus
353         * not be used in a finally block! More invocations of {@code getChangeSetDto(...)} than of
354         * {@code endSyncFromRepository()} are totally fine.
355         */
356        void endSyncFromRepository();
357
358        /**
359         * Marks the end of a synchronisation <b>to</b> the remote repository behind this {@code RepoTransport}.
360         * <p>
361         * This method should be invoked after all changes in the client repository have been completely written
362         * into the remote repository behind this {@code RepoTransport}.
363         * @param fromLocalRevision the {@code localRevision} of the source-repository to which the destination
364         * repository is now synchronous.
365         */
366        void endSyncToRepository(long fromLocalRevision);
367
368        @Override
369        public void close();
370
371        void putParentConfigPropSetDto(ConfigPropSetDto parentConfigPropSetDto);
372
373        VersionInfoDto getVersionInfoDto();
374
375        RepositoryDto getClientRepositoryDto();
376}