package co.codewizards.cloudstore.core.repo.transport;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
import co.codewizards.cloudstore.core.dto.ChangeSetDto;
import co.codewizards.cloudstore.core.dto.ConfigPropSetDto;
import co.codewizards.cloudstore.core.dto.DirectoryDto;
import co.codewizards.cloudstore.core.dto.NormalFileDto;
import co.codewizards.cloudstore.core.dto.RepoFileDto;
import co.codewizards.cloudstore.core.dto.RepositoryDto;
import co.codewizards.cloudstore.core.dto.SymlinkDto;
import co.codewizards.cloudstore.core.dto.VersionInfoDto;
import co.codewizards.cloudstore.core.oio.File;
import co.codewizards.cloudstore.core.util.IOUtil;
/**
* Transport abstraction.
* <p>
* The naming in this interface assumes a local client talking to a remote repository. But the
* repository accessed via this transport does not need to be remote - it might be in the local
* file system!
* <p>
* More precisely:
* <p>
* "Remote repository" references the repository which is accessed via this transport layer. The word
* "remote" thus indicates that there <b>might</b> be some distance between here and wherever this repository
* is located.
* <p>
* "Client" should primarily be understood as <i>API client</i>, i.e. the code using the methods of this
* interface. The "client repository" is the repository for which some client code accesses this
* {@code RepoTransport}, therefore the "client repository" is used for repo-to-repo-authentication. Some
* methods in this interface can be used without authentication (i.e. anonymously) - therefore a "client
* repository" is optional.
* <p>
* The synchronisation logic accesses all repositories through this abstraction layer. Therefore,
* the synchronisation logic does not need to know any details about how to communicate with
* a repository.
* <p>
* There are currently two implementations:
* <ul>
* <li>file-system-based (for local repositories)
* <li>REST-based (for remote repositories)
* </ul>
* Further implementations might be written later.
* <p>
* An instance of an implementation of {@code RepoTransport} is obtained via the
* {@link RepoTransportFactory}.
* <p>
* <b>Important:</b> Implementors should <i>not</i> directly implement this interface, but instead sub-class
* {@link AbstractRepoTransport}!
* @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
*/
public interface RepoTransport extends AutoCloseable {
/**
* Gets the factory which created this instance.
* @return the factory which created this instance. Should never be <code>null</code>, if properly initialised.
* @see #setRepoTransportFactory(RepoTransportFactory)
*/
RepoTransportFactory getRepoTransportFactory();
/**
* Sets the factory which created this instance.
* @param repoTransportFactory the factory which created this instance. Must not be <code>null</code>.
* @see #getRepoTransportFactory()
*/
void setRepoTransportFactory(RepoTransportFactory repoTransportFactory);
/**
* Gets the remote repository's root URL, maybe including a {@linkplain #getPathPrefix() path-prefix}.
* <p>
* This is thus the remote repository's root URL as used to synchronise a certain local repository.
* The word "remote" should not be misunderstood as actually on another computer. It just means behind
* this transport abstraction.
* <p>
* In contrast to the {@link #getRemoteRootWithoutPathPrefix() remoteRootWithoutPathPrefix}, this is
* the connection point for the synchronisation, which might be a sub-directory, i.e. not the native
* root of the connected repository.
* @return the remote repository's root URL, maybe including a {@linkplain #getPathPrefix() path-prefix}.
* Never <code>null</code>, if properly initialised.
* @see #setRemoteRoot(URL)
*/
URL getRemoteRoot();
/**
* Sets the remote repository's root URL.
* <p>
* This URL is the point where the {@linkplain #getClientRepositoryId() client-repository} is connected
* to the repository managed by this {@code RepoTransport}.
* <p>
* You should never directly invoke this method! It is automatically called when creating a
* {@code RepoTransport} instance via the {@link RepoTransportFactory}.
* <p>
* Invoking this method twice with different {@code remoteRoot} values is not allowed. The {@code remoteRoot}
* cannot be changed after it was once set.
* @param remoteRoot the remote repository's root URL. It may be <code>null</code>, but this
* {@code RepoTransport} is only usable, after this method was invoked with a non-<code>null</code> value.
* @see #getRemoteRoot()
*/
void setRemoteRoot(URL remoteRoot);
/**
* Gets the client repository's unique identifier.
* <p>
* The word "client" does not necessarily mean a remote JVM. It merely means the API client accessing
* this {@code RepoTransport} API.
* <p>
* This property might be <code>null</code>. If it is not set, only operations which do not require
* repo-to-repo-authentication can be used.
* @return the client repository's identifier. Might be <code>null</code>.
* @see #setClientRepositoryId(UUID)
*/
UUID getClientRepositoryId();
/**
* Sets the client's repository identifier.
* @param clientRepositoryId the client's repository identifier. May be <code>null</code>.
* @see #getClientRepositoryId()
*/
void setClientRepositoryId(UUID clientRepositoryId);
/**
* Gets the remote repository's root URL without the {@linkplain #getPathPrefix() path-prefix}.
* <p>
* In other words, this is the repository's <b>native root</b>, even if the connection is established to a
* sub-directory.
* @return the remote repository's root URL without the {@linkplain #getPathPrefix() path-prefix}. Never
* <code>null</code>, if properly initialised.
*/
URL getRemoteRootWithoutPathPrefix();
/**
* Prefix for every path (as used in {@link #delete(String)} for example).
* <p>
* It is possible to connect to a repository at a sub-directory, i.e. not the repo's root. If this
* {@code RepoTransport} is connected to the repo's root, this {@code pathPrefix} is an empty string.
* But if this {@code RepoTransport} is connected to a sub-directory, this sub-directory will be the
* {@code pathPrefix}.
* <p>
* For example, if the {@link #getRemoteRoot() remoteRoot} is
* <code>"https://some.host/some/repo/Private+pictures/Wedding+%26+honeymoon"</code> and the
* {@link #getRemoteRootWithoutPathPrefix() remoteRootWithoutPathPrefix} is
* <code>"https://some.host/some/repo"</code>,
* then this {@code pathPrefix} will be <code>"/Private pictures/Wedding & honeymoon"</code>.
* <p>
* As shown in this example, the {@code pathPrefix} is - just like every other path - <b>not</b> encoded
* in any way! The separator for the path-segments inside this prefix is "/" on all operating systems.
* <p>
* The {@code RepoTransport} implementations use this prefix to calculate back and forth between the
* path relative to the connected {@code remoteRoot} and the complete path used in the repository.
*/
String getPathPrefix();
/**
* Prepend the {@link #getPathPrefix() pathPrefix} to the given {@code path}.
* @param path the path to be prepended. Must not be <code>null</code>.
* @return the complete path composed of the {@link #getPathPrefix() pathPrefix} and the given
* {@code path}. Never <code>null</code>.
* @see #unprefixPath(String)
* @see #getPathPrefix()
*/
String prefixPath(String path);
/**
* Cut the {@link #getPathPrefix() pathPrefix} from the given {@code path}.
* @param path the path to be shortened. Must not be <code>null</code>. Of course, this path
* must start with {@link #getPathPrefix() pathPrefix}.
* @return the new shortened path without the {@link #getPathPrefix() pathPrefix}. Never
* <code>null</code>.
* @see #prefixPath(String)
* @see #getPathPrefix()
*/
String unprefixPath(String path);
/**
* Gets the remote repository's repository-descriptor.
* <p>
* This operation does not require authentication! It can (and is regularly) invoked anonymously.
* @return the remote repository's repository-descriptor. Never <code>null</code>.
*/
RepositoryDto getRepositoryDto();
/**
* Get the remote repository's unique identifier.
* @return the repository's unique identifier.
*/
UUID getRepositoryId();
/**
* Gets the remote repository's public key.
* @return the remote repository's public key. Never <code>null</code>.
*/
byte[] getPublicKey();
/**
* Request to connect the {@linkplain #getClientRepositoryId() client repository} with
* {@linkplain #getRepositoryId() the remote repository}.
* @param publicKey the public key of the client repository which requests the connection. Must not be
* <code>null</code>.
*/
void requestRepoConnection(byte[] publicKey);
/**
* Gets the change-set from the remote repository.
* <p>
* The invocation of this method marks the beginning of a synchronisation. After the synchronisation is
* complete, the {@link #endSyncFromRepository()} method must be invoked to notify the remote repository
* that all changes contained in the change set have been successfully and completely written to the
* client repository.
* <p>
* The change-set is dependent on the client repository: Every client repository gets its own individual
* change-set. The remote repository tracks which changes need to be sent to the client. In normal
* operation, the same change is transferred only once. Under certain circumstances, however, the same
* change might be transferred multiple times and the client must cope with this! Such duplicate
* transfers happen, if the transfer is interrupted - i.e. the {@link #endSyncFromRepository()} was not
* invoked.
* <p>
* Please note that the DTOs in this {@link ChangeSetDto} do not need to be completely resolved. They
* might be incomplete in order to reduce the size of the {@link ChangeSetDto}. For example,
* {@link NormalFileDto#getFileChunkDtos() NormalFileDto.fileChunkDtos} is not populated. These details
* are separately requested, later - e.g. by {@link #getRepoFileDto(String)}.
* @param localSync <code>true</code> indicates that the remote repository should perform a local sync
* before calculating the change set. <code>false</code> indicates that the remote repository should
* abstain from a local sync. This flag is a hint and the remote repository does not need to adhere it.
* @return the change-set from the remote repository. Never <code>null</code>.
*/
ChangeSetDto getChangeSetDto(boolean localSync);
/**
* Notifies the destination repository that this change-set is about to be synced into it.
* @param changeSetDto the change-set from the other RepoTransport (the source of the sync). Never <code>null</code>.
*/
void prepareForChangeSetDto(ChangeSetDto changeSetDto);
/**
* Creates the specified directory (including all parent-directories as needed).
* <p>
* If the directory already exists, this is a noop.
* <p>
* If there is any obstruction in the way of this path (e.g. a normal file), it is moved away (renamed or simply deleted
* depending on the conflict resolution strategy).
* @param path the path of the directory. Must not be <code>null</code>. No matter which operating system is used,
* the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
* It is always relative to the repository's root directory.
* @param lastModified the {@linkplain IOUtil#getLastModifiedNoFollow(File) last-modified-timestamp} the newly created
* directory will be set to.
* May be <code>null</code> (in which case the {@code lastModified} property is not touched). This applies only to the
* actual directory and not to the parent-directories! The parent-directories' {@code lastModified} properties are never
* touched - even if the parent-directories are newly created.
*/
void makeDirectory(String path, Date lastModified);
void makeSymlink(String path, String target, Date lastModified);
void copy(String fromPath, String toPath);
void move(String fromPath, String toPath);
/**
* Deletes the file (or directory) specified by {@code path}.
* <p>
* If there is no such file (or directory), this method is a noop.
* <p>
* If {@code path} denotes a directory, all its children (if there are) are deleted recursively.
* @param path the path of the file (or directory) to be deleted. Must not be <code>null</code>. No matter which
* operating system is used, the separation-character is always '/'. This path may start with a "/", but there is no
* difference, if it does: It is always relative to the repository's root directory.
*/
void delete(String path);
/**
* Gets the data of the {@linkplain NormalFileDto file} (or {@linkplain DirectoryDto directory} or
* {@linkplain SymlinkDto symlink}) identified by the given {@code path}.
* @param path the path to the file.
* @return the data of the file referenced by {@code path}. Never <code>null</code>.
*/
RepoFileDto getRepoFileDto(String path);
/**
* Get the binary file data at the given {@code offset} and with the given {@code length}.
* <p>
* If the file was modified/deleted, this method should not fail, but simply return <code>null</code>
* or a result being shorter than the {@code length} specified.
* @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
* the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
* It is always relative to the repository's root directory.
* @param offset the offset of the first byte to be read (0-based).
* @param length the length of the data to be read. -1 to read from {@code offset} to the end of the file.
*/
byte[] getFileData(String path, long offset, int length);
/**
* Begins a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it).
* <p>
* Usually, this method creates the specified file in the file system (if necessary with parent-directories)
* and in the database. But this operation may be deferred until {@link #endPutFile(String, Date, long, String)}.
* <p>
* If the file is immediately created, it should not be synchronised to any other repository, yet! It should
* be ignored, until {@link #endPutFile(String, Date, long, String)} was called for it.
* <p>
* In normal operation, zero or more invocations of {@link #putFileData(String, long, byte[])} and
* finally one invocation of {@link #endPutFile(String, Date, long, String)} follow this method. However, this is not
* guaranteed and the file transfer may be interrupted. If it is resumed, later this method is called again,
* without {@link #endPutFile(String, Date, long, String)} ever having been called in between.
* @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
* the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
* It is always relative to the repository's root directory.
* @see #putFileData(String, long, byte[])
* @see #endPutFile(String, Date, long, String)
*/
void beginPutFile(String path);
/**
* Write a block of binary data into the file.
* <p>
* This method may only be called after {@link #beginPutFile(String)} and before {@link #endPutFile(String, Date, long, String)}.
* @param offset the 0-based position in the file at which the block should be written.
* @see #beginPutFile(String)
* @see #endPutFile(String, Date, long, String)
*/
void putFileData(String path, long offset, byte[] fileData);
/**
* Ends a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it).
* @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used,
* the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does:
* It is always relative to the repository's root directory.
* @param lastModified when was the file's last modification. Must not be <code>null</code>.
* @param length the length of the file in bytes. If the file already existed and was longer, it is
* truncated to this length.
* @param sha1 the SHA1 hash of the file. May be <code>null</code>. If it is given, the repository may
* log a warning, if the current file is different. It should not throw an exception, because it
* is a valid state that a file is modified (by another process) while it is transferred.
*/
void endPutFile(String path, Date lastModified, long length, String sha1);
/**
* Marks the end of a synchronisation <b>from</b> the remote repository behind this {@code RepoTransport}.
* <p>
* This method should be invoked after all changes indicated by {@link #getChangeSetDto(boolean)} have
* been completely written into the client repository.
* <p>
* After this method was invoked, {@link #getChangeSetDto(boolean)} will return the new changes only.
* New changes means all those changes that were accumulated after its last invocation - not after the
* invocation of this method! This method might be called some time after {@code getChangeSetDto(...)}
* and it must be guaranteed that changes done between {@code getChangeSetDto(...)} and
* {@code endSyncFromRepository()} are contained in the next invocation of {@code getChangeSetDto(...)}.
* <p>
* This method must not be invoked, if an error was encountered during the synchronisation! It must thus
* not be used in a finally block! More invocations of {@code getChangeSetDto(...)} than of
* {@code endSyncFromRepository()} are totally fine.
*/
void endSyncFromRepository();
/**
* Marks the end of a synchronisation <b>to</b> the remote repository behind this {@code RepoTransport}.
* <p>
* This method should be invoked after all changes in the client repository have been completely written
* into the remote repository behind this {@code RepoTransport}.
* @param fromLocalRevision the {@code localRevision} of the source-repository to which the destination
* repository is now synchronous.
*/
void endSyncToRepository(long fromLocalRevision);
@Override
public void close();
void putParentConfigPropSetDto(ConfigPropSetDto parentConfigPropSetDto);
VersionInfoDto getVersionInfoDto();
}