/* * 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 io.datakernel.async.CompletionCallback; import io.datakernel.async.ForwardingResultCallback; import io.datakernel.async.ResultCallback; import io.datakernel.eventloop.Eventloop; import io.datakernel.file.AsyncFile; import io.datakernel.stream.file.StreamFileReader; import io.datakernel.stream.file.StreamFileWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import static io.datakernel.stream.file.StreamFileReader.readFileFrom; import static io.datakernel.util.Preconditions.checkNotNull; import static java.nio.file.StandardOpenOption.*; public final class FileManager { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private static final OpenOption[] CREATE_OPTIONS = new OpenOption[]{WRITE, CREATE_NEW}; private final Eventloop eventloop; private final ExecutorService executor; private final Path storagePath; private int readerBufferSize = 256 * (1 << 10); // 256kb private FileManager(Eventloop eventloop, ExecutorService executor, Path storagePath) { this.eventloop = checkNotNull(eventloop); this.executor = checkNotNull(executor); this.storagePath = checkNotNull(storagePath); if (Files.notExists(storagePath)) { try { Files.createDirectories(storagePath); } catch (IOException ignore) { throw new AssertionError("Can't create storage directory"); } } } public static FileManager create(Eventloop eventloop, ExecutorService executor, Path storagePath) { return new FileManager(eventloop, executor, storagePath); } public void get(String fileName, final long startPosition, final ResultCallback<StreamFileReader> callback) { logger.trace("downloading file: {}, position: {}", fileName, startPosition); AsyncFile.open(eventloop, executor, storagePath.resolve(fileName), new OpenOption[]{READ}, new ForwardingResultCallback<AsyncFile>(callback) { @Override public void onResult(AsyncFile result) { logger.trace("{} opened", result); callback.setResult(readFileFrom(eventloop, result, readerBufferSize, startPosition)); } }); } public void save(String fileName, final ResultCallback<StreamFileWriter> callback) { logger.trace("uploading file: {}", fileName); ensureDirectory(storagePath, fileName, new ForwardingResultCallback<Path>(callback) { @Override public void onResult(Path path) { logger.trace("ensured directory: {}", storagePath); AsyncFile.open(eventloop, executor, path, CREATE_OPTIONS, new ForwardingResultCallback<AsyncFile>(callback) { @Override public void onResult(AsyncFile result) { logger.trace("{} opened", result); callback.setResult(StreamFileWriter.create(eventloop, result, true)); } }); } }); } public void delete(String fileName, CompletionCallback callback) { logger.trace("deleting file: {}", fileName); AsyncFile.delete(eventloop, executor, storagePath.resolve(fileName), callback); } public void size(String fileName, ResultCallback<Long> callback) { logger.trace("calculating file size: {}", fileName); AsyncFile.length(eventloop, executor, storagePath.resolve(fileName), callback); } public void scan(ResultCallback<List<String>> callback) { eventloop.callConcurrently(executor, new Callable<List<String>>() { @Override public List<String> call() throws Exception { logger.trace("listing files"); List<String> result = new ArrayList<>(); doScan(storagePath, result, ""); return result; } }, callback); } public List<String> scan() throws IOException { List<String> result = new ArrayList<>(); doScan(storagePath, result, ""); return result; } private void doScan(Path parent, List<String> files, String pathFromRoot) throws IOException { try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(parent)) { for (Path path : directoryStream) { if (Files.isDirectory(path)) { doScan(path, files, pathFromRoot + path.getFileName().toString() + File.separator); } else { files.add(pathFromRoot + path.getFileName().toString()); } } } } private void ensureDirectory(final Path container, final String path, ResultCallback<Path> callback) { eventloop.callConcurrently(executor, new Callable<Path>() { @Override public Path call() throws Exception { return ensureDirectory(container, path); } }, callback); } private Path ensureDirectory(Path container, String path) throws IOException { String file; String filePath; if (path.contains(File.separator)) { int index = path.lastIndexOf(File.separator); file = path.substring(index + 1); filePath = path.substring(0, index); } else { file = path; filePath = ""; } Path destinationDirectory = container.resolve(filePath); Files.createDirectories(destinationDirectory); return destinationDirectory.resolve(file); } }