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 & 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 * @return the change-set from the remote repository. Never <code>null</code>. 225 */ 226 ChangeSetDto getChangeSetDto(boolean localSync); 227 228 /** 229 * Notifies the destination repository that this change-set is about to be synced into it. 230 * @param changeSetDto the change-set from the other RepoTransport (the source of the sync). Never <code>null</code>. 231 */ 232 void prepareForChangeSetDto(ChangeSetDto changeSetDto); 233 234 /** 235 * Creates the specified directory (including all parent-directories as needed). 236 * <p> 237 * If the directory already exists, this is a noop. 238 * <p> 239 * If there is any obstruction in the way of this path (e.g. a normal file), it is moved away (renamed or simply deleted 240 * depending on the conflict resolution strategy). 241 * @param path the path of the directory. Must not be <code>null</code>. No matter which operating system is used, 242 * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does: 243 * It is always relative to the repository's root directory. 244 * @param lastModified the {@linkplain IOUtil#getLastModifiedNoFollow(File) last-modified-timestamp} the newly created 245 * directory will be set to. 246 * May be <code>null</code> (in which case the {@code lastModified} property is not touched). This applies only to the 247 * actual directory and not to the parent-directories! The parent-directories' {@code lastModified} properties are never 248 * touched - even if the parent-directories are newly created. 249 */ 250 void makeDirectory(String path, Date lastModified); 251 252 void makeSymlink(String path, String target, Date lastModified); 253 254 void copy(String fromPath, String toPath); 255 void move(String fromPath, String toPath); 256 257 /** 258 * Deletes the file (or directory) specified by {@code path}. 259 * <p> 260 * If there is no such file (or directory), this method is a noop. 261 * <p> 262 * If {@code path} denotes a directory, all its children (if there are) are deleted recursively. 263 * @param path the path of the file (or directory) to be deleted. Must not be <code>null</code>. No matter which 264 * operating system is used, the separation-character is always '/'. This path may start with a "/", but there is no 265 * difference, if it does: It is always relative to the repository's root directory. 266 */ 267 void delete(String path); 268 269 /** 270 * Gets the data of the {@linkplain NormalFileDto file} (or {@linkplain DirectoryDto directory} or 271 * {@linkplain SymlinkDto symlink}) identified by the given {@code path}. 272 * @param path the path to the file. 273 * @return the data of the file referenced by {@code path}. Never <code>null</code>. 274 */ 275 RepoFileDto getRepoFileDto(String path); 276 277 /** 278 * Get the binary file data at the given {@code offset} and with the given {@code length}. 279 * <p> 280 * If the file was modified/deleted, this method should not fail, but simply return <code>null</code> 281 * or a result being shorter than the {@code length} specified. 282 * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used, 283 * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does: 284 * It is always relative to the repository's root directory. 285 * @param offset the offset of the first byte to be read (0-based). 286 * @param length the length of the data to be read. -1 to read from {@code offset} to the end of the file. 287 */ 288 byte[] getFileData(String path, long offset, int length); 289 290 /** 291 * Begins a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it). 292 * <p> 293 * Usually, this method creates the specified file in the file system (if necessary with parent-directories) 294 * and in the database. But this operation may be deferred until {@link #endPutFile(String, Date, long, String)}. 295 * <p> 296 * If the file is immediately created, it should not be synchronised to any other repository, yet! It should 297 * be ignored, until {@link #endPutFile(String, Date, long, String)} was called for it. 298 * <p> 299 * In normal operation, zero or more invocations of {@link #putFileData(String, long, byte[])} and 300 * finally one invocation of {@link #endPutFile(String, Date, long, String)} follow this method. However, this is not 301 * guaranteed and the file transfer may be interrupted. If it is resumed, later this method is called again, 302 * without {@link #endPutFile(String, Date, long, String)} ever having been called in between. 303 * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used, 304 * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does: 305 * It is always relative to the repository's root directory. 306 * @see #putFileData(String, long, byte[]) 307 * @see #endPutFile(String, Date, long, String) 308 */ 309 void beginPutFile(String path); 310 311 /** 312 * Write a block of binary data into the file. 313 * <p> 314 * This method may only be called after {@link #beginPutFile(String)} and before {@link #endPutFile(String, Date, long, String)}. 315 * @param offset the 0-based position in the file at which the block should be written. 316 * @see #beginPutFile(String) 317 * @see #endPutFile(String, Date, long, String) 318 */ 319 void putFileData(String path, long offset, byte[] fileData); 320 321 /** 322 * Ends a file transfer to this {@code RepoTransport} (more precisely the remote repository behind it). 323 * @param path the path of the file. Must not be <code>null</code>. No matter which operating system is used, 324 * the separation-character is always '/'. This path may start with a "/", but there is no difference, if it does: 325 * It is always relative to the repository's root directory. 326 * @param lastModified when was the file's last modification. Must not be <code>null</code>. 327 * @param length the length of the file in bytes. If the file already existed and was longer, it is 328 * truncated to this length. 329 * @param sha1 the SHA1 hash of the file. May be <code>null</code>. If it is given, the repository may 330 * log a warning, if the current file is different. It should not throw an exception, because it 331 * is a valid state that a file is modified (by another process) while it is transferred. 332 */ 333 void endPutFile(String path, Date lastModified, long length, String sha1); 334 335 /** 336 * Marks the end of a synchronisation <b>from</b> the remote repository behind this {@code RepoTransport}. 337 * <p> 338 * This method should be invoked after all changes indicated by {@link #getChangeSetDto(boolean)} have 339 * been completely written into the client repository. 340 * <p> 341 * After this method was invoked, {@link #getChangeSetDto(boolean)} will return the new changes only. 342 * New changes means all those changes that were accumulated after its last invocation - not after the 343 * invocation of this method! This method might be called some time after {@code getChangeSetDto(...)} 344 * and it must be guaranteed that changes done between {@code getChangeSetDto(...)} and 345 * {@code endSyncFromRepository()} are contained in the next invocation of {@code getChangeSetDto(...)}. 346 * <p> 347 * This method must not be invoked, if an error was encountered during the synchronisation! It must thus 348 * not be used in a finally block! More invocations of {@code getChangeSetDto(...)} than of 349 * {@code endSyncFromRepository()} are totally fine. 350 */ 351 void endSyncFromRepository(); 352 353 /** 354 * Marks the end of a synchronisation <b>to</b> the remote repository behind this {@code RepoTransport}. 355 * <p> 356 * This method should be invoked after all changes in the client repository have been completely written 357 * into the remote repository behind this {@code RepoTransport}. 358 * @param fromLocalRevision the {@code localRevision} of the source-repository to which the destination 359 * repository is now synchronous. 360 */ 361 void endSyncToRepository(long fromLocalRevision); 362 363 @Override 364 public void close(); 365 366 void putParentConfigPropSetDto(ConfigPropSetDto parentConfigPropSetDto); 367 368 VersionInfoDto getVersionInfoDto(); 369}