/* * 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.logfs; import io.datakernel.async.CompletionCallback; import io.datakernel.async.ForwardingResultCallback; import io.datakernel.async.ResultCallback; import io.datakernel.bytebuf.ByteBuf; import io.datakernel.eventloop.Eventloop; import io.datakernel.file.AsyncFile; import io.datakernel.stream.StreamConsumer; import io.datakernel.stream.StreamProducer; import io.datakernel.stream.StreamProducers; import io.datakernel.stream.file.StreamFileReader; import io.datakernel.stream.file.StreamFileWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import static java.nio.file.StandardOpenOption.READ; /** * Represents a file system for persisting logs. Stores files in a local file system. */ public final class LocalFsLogFileSystem extends AbstractLogFileSystem { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Eventloop eventloop; private final ExecutorService executorService; private final Path dir; /** * Constructs a log file system, that runs in the given event loop, runs blocking IO operations in the specified executor, * stores logs in the given directory. * * @param eventloop event loop, which log file system is to run * @param executorService executor for blocking IO operations * @param dir directory for storing log files */ private LocalFsLogFileSystem(Eventloop eventloop, ExecutorService executorService, Path dir) { this.eventloop = eventloop; this.executorService = executorService; this.dir = dir; } private LocalFsLogFileSystem(Eventloop eventloop, ExecutorService executorService, Path dir, String logName) { this.eventloop = eventloop; this.executorService = executorService; this.dir = dir.resolve(logName); } public static LocalFsLogFileSystem create(Eventloop eventloop, ExecutorService executorService, Path dir) { return new LocalFsLogFileSystem(eventloop, executorService, dir); } public static LocalFsLogFileSystem create(Eventloop eventloop, ExecutorService executorService, Path dir, String logName) { return new LocalFsLogFileSystem(eventloop, executorService, dir, logName); } private Path path(String logPartition, LogFile logFile) { return dir.resolve(fileName(logPartition, logFile)); } @Override public void list(final String logPartition, final ResultCallback<List<LogFile>> callback) { final Eventloop.ConcurrentOperationTracker concurrentOperationTracker = eventloop.startConcurrentOperation(); try { executorService.execute(new Runnable() { @Override public void run() { final List<LogFile> entries = new ArrayList<>(); try { Files.createDirectories(dir); Files.walkFileTree(dir, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { PartitionAndFile partitionAndFile = parse(file.getFileName().toString()); if (partitionAndFile != null && partitionAndFile.logPartition.equals(logPartition)) { entries.add(partitionAndFile.logFile); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { if (exc != null) { logger.error("visitFileFailed error", exc); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc != null) { logger.error("postVisitDirectory error", exc); } return FileVisitResult.CONTINUE; } }); eventloop.execute(new Runnable() { @Override public void run() { callback.setResult(entries); } }); } catch (final IOException e) { // TODO ? logger.error("walkFileTree error", e); eventloop.execute(new Runnable() { @Override public void run() { callback.setException(e); } }); } concurrentOperationTracker.complete(); } }); } catch (RejectedExecutionException e) { concurrentOperationTracker.complete(); callback.setException(e); } } @Override public void read(String logPartition, LogFile logFile, final long startPosition, final StreamConsumer<ByteBuf> consumer) { AsyncFile.open(eventloop, executorService, path(logPartition, logFile), new OpenOption[]{READ}, new ResultCallback<AsyncFile>() { @Override protected void onResult(AsyncFile file) { StreamFileReader fileReader = StreamFileReader.readFileFrom(eventloop, file, 1024 * 1024, startPosition); fileReader.streamTo(consumer); } @Override protected void onException(Exception e) { StreamProducers.<ByteBuf>closingWithError(eventloop, e).streamTo(consumer); } }); } @Override public void write(String logPartition, LogFile logFile, final StreamProducer<ByteBuf> producer, final CompletionCallback callback) { AsyncFile.open(eventloop, executorService, path(logPartition, logFile), StreamFileWriter.CREATE_OPTIONS, new ForwardingResultCallback<AsyncFile>(callback) { @Override protected void onResult(AsyncFile file) { StreamFileWriter fileWriter = StreamFileWriter.create(eventloop, file, true); producer.streamTo(fileWriter); fileWriter.setFlushCallback(callback); } }); } }