/*
* Copyright 2016 The Simple File Server Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sfs.filesystem.volume;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import io.vertx.core.Context;
import io.vertx.core.logging.Logger;
import org.sfs.Server;
import org.sfs.SfsVertx;
import org.sfs.VertxContext;
import org.sfs.rx.ObservableFuture;
import org.sfs.rx.RxHelper;
import org.sfs.rx.ToVoid;
import rx.Observable;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.copyOf;
import static io.vertx.core.logging.LoggerFactory.getLogger;
import static java.lang.String.valueOf;
import static java.nio.file.Files.createDirectory;
import static java.nio.file.Files.isDirectory;
import static org.sfs.filesystem.volume.Volume.Status.STARTED;
import static org.sfs.filesystem.volume.Volume.Status.STOPPED;
import static org.sfs.rx.Defer.aVoid;
import static org.sfs.rx.RxHelper.iterate;
import static rx.Observable.defer;
public class VolumeManager {
private static final Logger LOGGER = getLogger(VolumeManager.class);
private Map<String, Volume> volumeMap = new HashMap<>();
private Path basePath;
private boolean open = false;
public VolumeManager(Path basePath) {
this.basePath = Paths.get(basePath.toString(), "volumes");
}
public boolean isOpen() {
return open;
}
public Observable<Void> open(VertxContext<Server> vertxContext) {
return defer(() -> {
open = true;
ObservableFuture<Void> handler = RxHelper.observableFuture();
vertxContext.vertx().fileSystem()
.mkdirs(basePath.toString(), null, handler.toHandler());
return handler
.flatMap(aVoid -> {
ObservableFuture<List<String>> handler1 = RxHelper.observableFuture();
vertxContext.vertx().fileSystem()
.readDir(basePath.toString(), handler1.toHandler());
return handler1;
})
.flatMap(Observable::from)
.map(volumeDirectory -> Paths.get(volumeDirectory))
.filter(volumeDirectory -> {
checkState(isDirectory(volumeDirectory), "%s must be a directory", volumeDirectory.toString());
return true;
})
.flatMap(volumeDirectory -> {
final Volume volume = new VolumeV1(volumeDirectory);
return volume.open(vertxContext.vertx())
.map(aVoid -> volume);
})
.map(volume -> {
volumeMap.put(volume.getVolumeId(), volume);
return null;
})
.count()
.map(new ToVoid<>())
.singleOrDefault(null)
.flatMap(o -> {
if (volumeMap.isEmpty()) {
return newVolume(vertxContext)
.map(new ToVoid<>());
}
return aVoid();
});
});
}
public Observable<Void> deleteVolumes(VertxContext<Server> vertxContext) {
return aVoid()
.doOnNext(aVoid -> {
checkState(!open, "Not closed");
})
.flatMap(aVoid -> {
ObservableFuture<Void> handler = RxHelper.observableFuture();
vertxContext.vertx().fileSystem().deleteRecursive(basePath.toString(), true, handler.toHandler());
return handler;
});
}
public Observable<String> newVolume(VertxContext<Server> vertxContext) {
return newVolume0(vertxContext, 0)
.map(volume -> {
volumeMap.put(volume.getVolumeId(), volume);
return volume.getVolumeId();
});
}
protected Observable<Volume> newVolume0(VertxContext<Server> vertxContext, final int offset) {
SfsVertx sfsVertx = vertxContext.vertx();
Context context = sfsVertx.getOrCreateContext();
return defer(() -> {
final Path path = Paths.get(basePath.toString(), valueOf(volumeMap.size() + offset));
AtomicBoolean exists = new AtomicBoolean(false);
return RxHelper.executeBlocking(context, sfsVertx.getBackgroundPool(),
() -> {
try {
createDirectory(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
return (Void) null;
})
.onErrorResumeNext(throwable -> {
exists.set(true);
return aVoid();
})
.flatMap(aVoid -> {
if (!exists.get()) {
final Volume volume = new VolumeV1(path);
return volume.open(vertxContext.vertx())
.map(aVoid1 -> volume);
} else {
return newVolume0(vertxContext, offset + 1);
}
});
});
}
public Observable<Void> openVolume(VertxContext<Server> vertxContext, final String volumeId) {
return aVoid()
.flatMap(aVoid -> {
Volume volume = volumeMap.get(volumeId);
if (volume == null) {
throw new VolumeNotFoundException("Volume " + volumeId + " is not managed by this volume manager");
}
if (STOPPED.equals(volume.status())) {
return volume.open(vertxContext.vertx());
} else {
throw new VolumeNotStoppedException();
}
});
}
public Observable<Void> closeVolume(VertxContext<Server> vertxContext, final String volumeId) {
return aVoid()
.flatMap(aVoid -> {
Volume volume = volumeMap.get(volumeId);
if (volume == null) {
throw new VolumeNotFoundException("Volume " + volumeId + " is not managed by this volume manager");
}
if (STARTED.equals(volume.status())) {
return volume.open(vertxContext.vertx());
} else {
throw new VolumeNotStartedException();
}
});
}
public Iterable<String> volumes() {
return FluentIterable.from(volumeMap.values())
.transform(input -> input.getVolumeId());
}
public Observable<Void> close(VertxContext<Server> vertxContext) {
return defer(() -> {
open = false;
final ImmutableSet<Volume> values = copyOf(volumeMap.values());
volumeMap.clear();
return iterate(
vertxContext.vertx(),
values, volume -> volume.close(vertxContext.vertx())
.onErrorResumeNext(throwable -> {
LOGGER.warn("Unhandled Exception", throwable);
return aVoid();
})
.map(aVoid -> true)
)
.map(new ToVoid<>());
});
}
public Optional<Volume> get(String volumeId) {
return fromNullable(volumeMap.get(volumeId));
}
public static class VolumeNotFoundException extends RuntimeException {
public VolumeNotFoundException(String message) {
super(message);
}
}
public static class VolumeNotStoppedException extends RuntimeException {
}
public static class VolumeNotStartedException extends RuntimeException {
}
}