/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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 io.datakernel.remotefs;
import com.google.gson.Gson;
import io.datakernel.async.*;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.eventloop.AbstractServer;
import io.datakernel.eventloop.AsyncTcpSocket;
import io.datakernel.eventloop.AsyncTcpSocket.EventHandler;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.stream.StreamConsumer;
import io.datakernel.stream.StreamProducer;
import io.datakernel.stream.file.StreamFileReader;
import io.datakernel.stream.file.StreamFileWriter;
import io.datakernel.stream.net.Messaging.ReceiveMessageCallback;
import io.datakernel.stream.net.MessagingSerializer;
import io.datakernel.stream.net.MessagingWithBinaryStreaming;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import static io.datakernel.stream.net.MessagingSerializers.ofGson;
public final class RemoteFsServer extends AbstractServer<RemoteFsServer> {
protected final FileManager fileManager;
private MessagingSerializer<RemoteFsCommands.FsCommand, RemoteFsResponses.FsResponse> serializer = ofGson(getCommandGSON(), RemoteFsCommands.FsCommand.class, getResponseGson(), RemoteFsResponses.FsResponse.class);
private final Map<Class, MessagingHandler> handlers;
// region creators & builder methods
private RemoteFsServer(Eventloop eventloop, FileManager fileManager) {
super(eventloop);
this.fileManager = fileManager;
this.handlers = createHandlers();
}
public static RemoteFsServer create(Eventloop eventloop, ExecutorService executor, Path storage) {
return new RemoteFsServer(eventloop, FileManager.create(eventloop, executor, storage));
}
// endregion
// public api
public void upload(String fileName, final ResultCallback<StreamConsumer<ByteBuf>> callback) {
fileManager.save(fileName, new ForwardingResultCallback<StreamFileWriter>(callback) {
@Override
public void onResult(StreamFileWriter result) {
callback.setResult(result);
}
});
}
public void download(String fileName, long startPosition, final ResultCallback<StreamProducer<ByteBuf>> callback) {
fileManager.get(fileName, startPosition, new ForwardingResultCallback<StreamFileReader>(callback) {
@Override
public void onResult(StreamFileReader result) {
callback.setResult(result);
}
});
}
public void delete(String fileName, CompletionCallback callback) {
fileManager.delete(fileName, callback);
}
protected void list(ResultCallback<List<String>> callback) {
fileManager.scan(callback);
}
// set up connection
@Override
protected final EventHandler createSocketHandler(AsyncTcpSocket asyncTcpSocket) {
final MessagingWithBinaryStreaming<RemoteFsCommands.FsCommand, RemoteFsResponses.FsResponse> messaging = MessagingWithBinaryStreaming.create(eventloop, asyncTcpSocket, serializer);
messaging.receive(new ReceiveMessageCallback<RemoteFsCommands.FsCommand>() {
@Override
public void onReceive(RemoteFsCommands.FsCommand msg) {
logger.trace("received {}", msg);
doRead(messaging, msg);
}
@Override
public void onReceiveEndOfStream() {
logger.warn("unexpected end of stream");
messaging.close();
}
@Override
public void onException(Exception e) {
logger.error("received error while reading", e);
messaging.close();
}
});
return messaging;
}
private void doRead(MessagingWithBinaryStreaming<RemoteFsCommands.FsCommand, RemoteFsResponses.FsResponse> messaging, RemoteFsCommands.FsCommand item) {
MessagingHandler handler = handlers.get(item.getClass());
if (handler == null) {
messaging.close();
logger.error("missing handler for " + item);
} else {
//noinspection unchecked
handler.onMessage(messaging, item);
}
}
protected Gson getResponseGson() {
return RemoteFsResponses.responseGson;
}
protected Gson getCommandGSON() {
return RemoteFsCommands.commandGSON;
}
protected interface MessagingHandler<I, O> {
void onMessage(MessagingWithBinaryStreaming<I, O> messaging, I item);
}
private Map<Class, MessagingHandler> createHandlers() {
Map<Class, MessagingHandler> map = new HashMap<>();
map.put(RemoteFsCommands.Upload.class, new UploadMessagingHandler());
map.put(RemoteFsCommands.Download.class, new DownloadMessagingHandler());
map.put(RemoteFsCommands.Delete.class, new DeleteMessagingHandler());
map.put(RemoteFsCommands.ListFiles.class, new ListFilesMessagingHandler());
return map;
}
// handler classes
private class UploadMessagingHandler implements MessagingHandler<RemoteFsCommands.Upload, RemoteFsResponses.FsResponse> {
@Override
public void onMessage(final MessagingWithBinaryStreaming<RemoteFsCommands.Upload, RemoteFsResponses.FsResponse> messaging, final RemoteFsCommands.Upload item) {
upload(item.filePath, new ResultCallback<StreamConsumer<ByteBuf>>() {
@Override
public void onResult(StreamConsumer<ByteBuf> result) {
messaging.receiveBinaryStreamTo(result, new ForwardingCompletionCallback(this) {
@Override
public void onComplete() {
logger.trace("read all bytes for {}", item.filePath);
messaging.send(new RemoteFsResponses.Acknowledge(), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
});
}
@Override
public void onException(Exception exception) {
messaging.close();
}
});
}
}
private class DownloadMessagingHandler implements MessagingHandler<RemoteFsCommands.Download, RemoteFsResponses.FsResponse> {
@Override
public void onMessage(final MessagingWithBinaryStreaming<RemoteFsCommands.Download, RemoteFsResponses.FsResponse> messaging, final RemoteFsCommands.Download item) {
fileManager.size(item.filePath, new ResultCallback<Long>() {
@Override
public void onResult(final Long size) {
if (size < 0) {
messaging.send(new RemoteFsResponses.Err("File not found"), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
} else {
messaging.send(new RemoteFsResponses.Ready(size), new ForwardingCompletionCallback(this) {
@Override
public void onComplete() {
download(item.filePath, item.startPosition, new ForwardingResultCallback<StreamProducer<ByteBuf>>(this) {
@Override
public void onResult(final StreamProducer<ByteBuf> result) {
messaging.sendBinaryStreamFrom(result, new CompletionCallback() {
@Override
protected void onException(Exception e) {
onCompleteOrException();
}
@Override
protected void onComplete() {
onCompleteOrException();
}
void onCompleteOrException() {
messaging.close();
}
});
}
});
}
});
}
}
@Override
public void onException(Exception e) {
messaging.send(new RemoteFsResponses.Err(e.getMessage()), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
});
}
}
private class DeleteMessagingHandler implements MessagingHandler<RemoteFsCommands.Delete, RemoteFsResponses.FsResponse> {
@Override
public void onMessage(final MessagingWithBinaryStreaming<RemoteFsCommands.Delete, RemoteFsResponses.FsResponse> messaging, final RemoteFsCommands.Delete item) {
delete(item.filePath, new CompletionCallback() {
@Override
public void onComplete() {
messaging.send(new RemoteFsResponses.Ok(), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
@Override
public void onException(Exception e) {
messaging.send(new RemoteFsResponses.Err(e.getMessage()), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
});
}
}
private class ListFilesMessagingHandler implements MessagingHandler<RemoteFsCommands.ListFiles, RemoteFsResponses.FsResponse> {
@Override
public void onMessage(final MessagingWithBinaryStreaming<RemoteFsCommands.ListFiles, RemoteFsResponses.FsResponse> messaging, RemoteFsCommands.ListFiles item) {
list(new ResultCallback<List<String>>() {
@Override
public void onResult(List<String> result) {
messaging.send(new RemoteFsResponses.ListOfFiles(result), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
@Override
public void onException(Exception e) {
messaging.send(new RemoteFsResponses.Err(e.getMessage()), IgnoreCompletionCallback.create());
messaging.sendEndOfStream(IgnoreCompletionCallback.create());
}
});
}
}
}