All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kvd.server.storage.fs.FileStorage Maven / Gradle / Ivy

/*
 * Copyright 2020 Andre Gebers
 *
 * 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 kvd.server.storage.fs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import kvd.common.KvdException;
import kvd.server.storage.AbortableOutputStream;
import kvd.server.storage.KeyUtils;
import kvd.server.storage.StorageBackend;

public class FileStorage implements StorageBackend {

  private static final Logger log = LoggerFactory.getLogger(FileStorage.class);

  private static class Staging {

    private String key;

    private File f;

    public Staging(String key, File f) {
      super();
      this.key = key;
      this.f = f;
    }

    public String getKey() {
      return key;
    }

    public File getFile() {
      return f;
    }
  }

  public File base;

  public File storage;

  public File staging;

  private Map wmap = new HashMap<>();

  public FileStorage(File base) {
    super();
    this.base = base;
    this.storage = new File(base, "store");
    this.staging = new File(base, "staging");
    createDirIfMissing(base);
    createDirIfMissing(storage);
    createDirIfMissing(staging);
  }

  private void createDirIfMissing(File d) {
    if(!d.exists()) {
      if(!d.mkdirs()) {
        throw new KvdException(String.format("'%s' does not exist and failed to create", d.getAbsolutePath()));
      }
    }
    if(!d.isDirectory()) {
      throw new KvdException(String.format("'%s' exists but not directory", d.getAbsolutePath()));
    }
    // TODO write test
  }

  @Override
  public synchronized AbortableOutputStream put(String key) {
    String txId = UUID.randomUUID().toString();
    File f = new File(staging, txId);
    try {
      AbortableOutputStream out = new AbortableOutputStream(
          new BufferedOutputStream(new FileOutputStream(f)),
          txId,
          this::commit,
          this::rollback);
      Staging staging = new Staging(KeyUtils.internalKey(key), f);
      wmap.put(txId, staging);
      log.debug("starting put, key '{}', tx '{}'", staging.getKey(), txId);
      return out;
    } catch(Exception e) {
      throw new KvdException("failed to create staging file for key " + key, e);
    }
  }

  private synchronized void commit(String txId) {
    Staging staging = wmap.get(txId);
    try {
      if(staging != null) {
        Path to = new File(storage, staging.getKey()).toPath();
        Path from = staging.getFile().toPath();
        Files.move(from, to, StandardCopyOption.REPLACE_EXISTING);
      } else {
        log.warn("staging file missing, {}", txId);
      }
    } catch(Exception e) {
      rollback(txId);
      throw new KvdException("failed to finish put " + txId);
    } finally {
      wmap.remove(txId);
    }
  }

  private synchronized void rollback(String txId) {
    Staging staging = wmap.get(txId);
    if(staging != null) {
      File f = staging.getFile();
      if(f.exists()) {
        if(!f.delete()) {
          log.warn("delete failed for '{}' on abort, {}", f.getAbsolutePath(), txId);
        }
      }
    }
  }

  @Override
  public InputStream get(String key) {
    try {
      String s = KeyUtils.internalKey(key);
      File f = new File(storage, s);
      if(f.canRead()) {
        return new BufferedInputStream(new FileInputStream(f));
      } else {
        return null;
      }
    } catch(Exception e) {
      throw new KvdException("failed to get key " + key, e);
    }
  }

  @Override
  public boolean contains(String key) {
    String s = KeyUtils.internalKey(key);
    File f = new File(storage, s);
    return f.canRead();
  }

  @Override
  public boolean remove(String key) {
    String s = KeyUtils.internalKey(key);
    File f = new File(storage, s);
    if(f.exists()) {
      boolean removed = f.delete();
      if(!removed) {
        log.warn("file '{}' could not be removed", f.getAbsolutePath());
      }
      return removed;
    } else {
      return false;
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy