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

org.cryptomator.cryptofs.fh.OpenCryptoFiles Maven / Gradle / Ivy

There is a newer version: 2.7.1-beta1
Show newest version
/*******************************************************************************
 * Copyright (c) 2016 Sebastian Stenzel and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the accompanying LICENSE.txt.
 *
 * Contributors:
 *     Sebastian Stenzel - initial API and implementation
 *******************************************************************************/
package org.cryptomator.cryptofs.fh;

import org.cryptomator.cryptofs.CryptoFileSystemScoped;
import org.cryptomator.cryptofs.EffectiveOpenOptions;

import javax.inject.Inject;
import java.io.Closeable;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@CryptoFileSystemScoped
public class OpenCryptoFiles implements Closeable {

	private final OpenCryptoFileComponent.Factory openCryptoFileComponentFactory;
	private final ConcurrentMap openCryptoFiles = new ConcurrentHashMap<>();

	@Inject
	OpenCryptoFiles(OpenCryptoFileComponent.Factory openCryptoFileComponentFactory) {
		this.openCryptoFileComponentFactory = openCryptoFileComponentFactory;
	}

	/**
	 * Gets an OpenCryptoFile (if any is opened) without creating it.
	 * 

* Useful if you don't want to create any FileChannel but want to check whether this file is currently opened (e.g. to get its current {@link OpenCryptoFile#size()}). * * @param ciphertextPath Path of the file which might have been opened * @return The OpenCryptoFile if opened or an empty Optional otherwise. */ public Optional get(Path ciphertextPath) { Path normalizedPath = ciphertextPath.toAbsolutePath().normalize(); return Optional.ofNullable(openCryptoFiles.get(normalizedPath)); } /** * Opens a file to {@link OpenCryptoFile#newFileChannel(EffectiveOpenOptions, java.nio.file.attribute.FileAttribute[]) retrieve a FileChannel}. If this file is already opened, a shared instance is returned. * Getting the file channel should be the next invocation, since the {@link OpenFileScoped lifecycle} of the OpenFile strictly depends on the lifecycle of the channel. * * @param ciphertextPath Path of the file to open * @return The opened file. * @see #get(Path) */ public OpenCryptoFile getOrCreate(Path ciphertextPath) { Path normalizedPath = ciphertextPath.toAbsolutePath().normalize(); return openCryptoFiles.computeIfAbsent(normalizedPath, p -> openCryptoFileComponentFactory.create(p, openCryptoFiles::remove).openCryptoFile()); // computeIfAbsent is atomic, "create" is called at most once } public void writeCiphertextFile(Path ciphertextPath, EffectiveOpenOptions openOptions, ByteBuffer contents) throws IOException { try (OpenCryptoFile f = getOrCreate(ciphertextPath); FileChannel ch = f.newFileChannel(openOptions)) { ch.write(contents); } } public ByteBuffer readCiphertextFile(Path ciphertextPath, EffectiveOpenOptions openOptions, int maxBufferSize) throws BufferUnderflowException, IOException { try (OpenCryptoFile f = getOrCreate(ciphertextPath); FileChannel ch = f.newFileChannel(openOptions)) { if (ch.size() > maxBufferSize) { throw new BufferUnderflowException(); } ByteBuffer buf = ByteBuffer.allocate((int) ch.size()); // ch.size() <= maxBufferSize <= Integer.MAX_VALUE ch.read(buf); buf.flip(); return buf; } } /** * Removes a ciphertextPath to {@link OpenCryptoFile} mapping, if it exists, and sets the path of the openCryptoFile to null. * * @param ciphertextPath The ciphertext file path to invalidate */ public void delete(Path ciphertextPath) { openCryptoFiles.compute(ciphertextPath, (p, openFile) -> { if (openFile != null) { openFile.updateCurrentFilePath(null); } return null; }); } /** * Prepares to update any open file references during a move operation. * MUST be invoked using a try-with-resource statement and committed after the physical file move succeeded. * * @param src The ciphertext file path before the move * @param dst The ciphertext file path after the move * @return Utility to update OpenCryptoFile references. * @throws FileAlreadyExistsException Thrown if the destination file is an existing file that is currently opened. */ public TwoPhaseMove prepareMove(Path src, Path dst) throws FileAlreadyExistsException { return new TwoPhaseMove(src, dst); } /** * Close all currently opened files by invoking {@link OpenCryptoFile#close()}. */ @Override public void close() { Iterator> iter = openCryptoFiles.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); iter.remove(); // remove before invoking close() to avoid concurrent modification of this iterator by #close(OpenCryptoFile) entry.getValue().close(); } } public class TwoPhaseMove implements AutoCloseable { private final Path src; private final Path dst; private final OpenCryptoFile openCryptoFile; private boolean committed; private boolean rolledBack; private TwoPhaseMove(Path src, Path dst) throws FileAlreadyExistsException { this.src = Objects.requireNonNull(src); this.dst = Objects.requireNonNull(dst); try { // ConcurrentHashMap.compute is atomic: this.openCryptoFile = openCryptoFiles.compute(dst, (k, v) -> { if (v == null) { return openCryptoFiles.get(src); } else { throw new AlreadyMappedException(); } }); } catch (AlreadyMappedException e) { throw new FileAlreadyExistsException(dst.toString(), null, "Destination file currently accessed by another thread."); } } public void commit() { if (rolledBack) { throw new IllegalStateException(); } if (openCryptoFile != null) { openCryptoFile.updateCurrentFilePath(dst); } openCryptoFiles.remove(src, openCryptoFile); committed = true; } public void rollback() { if (committed) { throw new IllegalStateException(); } openCryptoFiles.remove(dst, openCryptoFile); rolledBack = true; } @Override public void close() { if (!committed) { rollback(); } } } private static class AlreadyMappedException extends RuntimeException { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy