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 * @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}