3737import java .util .Optional ;
3838import java .util .concurrent .CompletableFuture ;
3939import java .util .concurrent .CompletionStage ;
40-
40+ import java .util .concurrent .locks .Lock ;
41+ import java .util .concurrent .locks .ReadWriteLock ;
42+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
43+
44+ /**
45+ * CloudProvider implementation to mirror a folder in the local filesystem.
46+ * <p>
47+ * This class is mainly for testing purposes and therefore aims for correctness, not performance.
48+ * All filesystem altering operations (create, delete, move and write) will be executed exclusively and blocking,
49+ * while all fs quering operations are performed simultanously.
50+ */
4151public class LocalFsCloudProvider implements CloudProvider {
4252
4353 private static final CloudPath ABS_ROOT = CloudPath .of ("/" );
4454
4555 private final Path root ;
4656
57+ /**
58+ * Lock to ensure that any operation always performed on a consistent filesystem, i.e. no pending fs-altering operation exists.
59+ */
60+ private final ReadWriteLock lock ;
61+
4762 public LocalFsCloudProvider (Path root ) {
4863 this .root = root ;
64+ this .lock = new ReentrantReadWriteLock ();
4965 }
5066
5167 private Path resolve (CloudPath cloudPath ) {
@@ -64,6 +80,8 @@ private CloudItemMetadata createMetadata(Path fullPath, BasicFileAttributes attr
6480 @ Override
6581 public CompletionStage <CloudItemMetadata > itemMetadata (CloudPath node ) {
6682 Path path = resolve (node );
83+ Lock l = lock .readLock ();
84+ l .lock ();
6785 try {
6886 var attr = Files .readAttributes (path , BasicFileAttributes .class , LinkOption .NOFOLLOW_LINKS );
6987 var metadata = createMetadata (path , attr );
@@ -72,12 +90,16 @@ public CompletionStage<CloudItemMetadata> itemMetadata(CloudPath node) {
7290 return CompletableFuture .failedFuture (new NotFoundException (e ));
7391 } catch (IOException e ) {
7492 return CompletableFuture .failedFuture (new CloudProviderException (e ));
93+ } finally {
94+ l .unlock ();
7595 }
7696 }
7797
7898 @ Override
7999 public CompletionStage <CloudItemList > list (CloudPath folder , Optional <String > pageToken ) {
80100 Path folderPath = resolve (folder );
101+ Lock l = lock .readLock ();
102+ l .lock ();
81103 try {
82104 List <CloudItemMetadata > items = new ArrayList <>();
83105 var provider = this ;
@@ -96,12 +118,16 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
96118 return CompletableFuture .failedFuture (new TypeMismatchException (e ));
97119 } catch (IOException e ) {
98120 return CompletableFuture .failedFuture (new CloudProviderException (e ));
121+ } finally {
122+ l .unlock ();
99123 }
100124 }
101125
102126 @ Override
103127 public CompletionStage <InputStream > read (CloudPath file , long offset , long count , ProgressListener progressListener ) {
104128 Path filePath = resolve (file );
129+ Lock l = lock .readLock ();
130+ l .lock ();
105131 try {
106132 var ch = Files .newByteChannel (filePath , StandardOpenOption .READ );
107133 ch .position (offset );
@@ -110,6 +136,8 @@ public CompletionStage<InputStream> read(CloudPath file, long offset, long count
110136 return CompletableFuture .failedFuture (new NotFoundException (e ));
111137 } catch (IOException e ) {
112138 return CompletableFuture .failedFuture (new CloudProviderException (e ));
139+ } finally {
140+ l .unlock ();
113141 }
114142 }
115143
@@ -120,6 +148,8 @@ public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace,
120148 ? EnumSet .of (StandardOpenOption .WRITE , StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING )
121149 : EnumSet .of (StandardOpenOption .WRITE , StandardOpenOption .CREATE_NEW );
122150
151+ Lock l = lock .writeLock ();
152+ l .lock ();
123153 try (var ch = FileChannel .open (filePath , options )) {
124154 var tmpSize = ch .transferFrom (Channels .newChannel (data ), 0 , Long .MAX_VALUE );
125155 var modifiedDate = Files .getLastModifiedTime (filePath ).toInstant ();
@@ -131,39 +161,51 @@ public CompletionStage<CloudItemMetadata> write(CloudPath file, boolean replace,
131161 return CompletableFuture .failedFuture (new AlreadyExistsException (e ));
132162 } catch (IOException e ) {
133163 return CompletableFuture .failedFuture (new CloudProviderException (e ));
164+ } finally {
165+ l .unlock ();
134166 }
135167 }
136168
137169 @ Override
138170 public CompletionStage <CloudPath > createFolder (CloudPath folder ) {
139171 Path folderPath = resolve (folder );
172+ Lock l = lock .writeLock ();
173+ l .lock ();
140174 try {
141175 Files .createDirectory (folderPath );
142176 return CompletableFuture .completedFuture (folder );
143177 } catch (FileAlreadyExistsException e ) {
144178 return CompletableFuture .failedFuture (new AlreadyExistsException (e ));
145179 } catch (IOException e ) {
146180 return CompletableFuture .failedFuture (new CloudProviderException (e ));
181+ } finally {
182+ l .unlock ();
147183 }
148184 }
149185
150186 @ Override
151187 public CompletionStage <Void > delete (CloudPath node ) {
152188 Path path = resolve (node );
189+ Lock l = lock .writeLock ();
190+ l .lock ();
153191 try {
154192 MoreFiles .deleteRecursively (path , RecursiveDeleteOption .ALLOW_INSECURE );
155193 return CompletableFuture .completedFuture (null );
156194 } catch (NoSuchFileException e ) {
157195 return CompletableFuture .failedFuture (new NotFoundException (e ));
158196 } catch (IOException e ) {
159197 return CompletableFuture .failedFuture (new CloudProviderException (e ));
198+ } finally {
199+ l .unlock ();
160200 }
161201 }
162202
163203 @ Override
164204 public CompletionStage <CloudPath > move (CloudPath source , CloudPath target , boolean replace ) {
165205 Path src = resolve (source );
166206 Path dst = resolve (target );
207+ Lock l = lock .writeLock ();
208+ l .lock ();
167209 try {
168210 var options = replace ? EnumSet .of (StandardCopyOption .REPLACE_EXISTING ) : EnumSet .noneOf (StandardCopyOption .class );
169211 Files .move (src , dst , options .toArray (CopyOption []::new ));
@@ -174,6 +216,8 @@ public CompletionStage<CloudPath> move(CloudPath source, CloudPath target, boole
174216 return CompletableFuture .failedFuture (new AlreadyExistsException (e ));
175217 } catch (IOException e ) {
176218 return CompletableFuture .failedFuture (new CloudProviderException (e ));
219+ } finally {
220+ l .unlock ();
177221 }
178222 }
179223}
0 commit comments