package fr.openwide.core.commons.util.registry;
import java.io.Closeable;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TVFS;
import de.schlichtherle.truezip.fs.FsSyncException;
import de.schlichtherle.truezip.fs.FsSyncOptions;
/**
* A registry responsible for keeping track of open {@link TFile TFiles} and for cleaning them up upon closing it.
* <p>This should generally be used as a factory for creating TFiles (see the <code>create</code> methods), and the
* calling of {@link #open()} and {@link #close()} should be done in some generic code (such as a servlet filter).
* <p>Please note that this registry is using a {@link ThreadLocal}. Opening and closing the registry must therefore
* be done in the same thread, and each TFile-creating thread should use its own registry.
*/
public final class TFileRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(TFileRegistry.class);
private static final ThreadLocal<TFileRegistryImpl> THREAD_LOCAL = new ThreadLocal<TFileRegistryImpl>();
private TFileRegistry() { }
/**
* Enables the (thread-local) TFileRegistry, so that every call to the createXX() or register() static methods
* will result in the relevant TFile to be stored for further cleaning of TrueZip internal resources.
* <p>The actual cleaning must be performed by calling the {@link #close()} static method on TFileRegistry.
*/
public static void open() {
TFileRegistryImpl registry = THREAD_LOCAL.get();
if (registry == null) {
THREAD_LOCAL.set(new TFileRegistryImpl());
} else {
throw new IllegalStateException("TFileRegistry.open() should not be called twice without calling close() in-between.");
}
}
/**
* {@link TVFS#sync(de.schlichtherle.truezip.fs.FsMountPoint, de.schlichtherle.truezip.util.BitField) Synchronize}
* the TrueZip virtual filesystem for every registered file, and clears the registry.
* <p><strong>WARNING :</strong> If some {@link InputStream InputStreams} or {@link OutputStream OutputStreams}
* managed by the current thread have not been closed yet, they will be ignored.
*/
public static void close() {
try {
TFileRegistryImpl registry = THREAD_LOCAL.get();
if (registry != null) {
registry.close();
} else {
throw new IllegalStateException("TFileRegistry.close() should not be called if TFileRegistry.open() has not been called before.");
}
} finally {
THREAD_LOCAL.remove();
}
}
public static TFile create(String path) {
TFile tFile = new TFile(path);
register(tFile);
return tFile;
}
public static TFile create(File file) {
TFile tFile = new TFile(file);
register(tFile);
return tFile;
}
public static TFile create(String parent, String member) {
TFile tFile = new TFile(parent, member);
register(tFile);
return tFile;
}
public static TFile create(File parent, String member) {
TFile tFile = new TFile(parent, member);
register(tFile);
return tFile;
}
public static TFile create(URI uri) {
TFile tFile = new TFile(uri);
register(tFile);
return tFile;
}
public static void register(File file) {
Objects.requireNonNull(file, "file must not be null");
TFileRegistryImpl registry = THREAD_LOCAL.get();
if (registry != null) {
registry.register(file);
} else {
LOGGER.info("Trying to register file '{}', but the TFileRegistry has not been open (see TFileRegistry.open()). Ignoring registration.", file);
THREAD_LOCAL.remove();
}
}
public static void register(Iterable<? extends File> files) {
Objects.requireNonNull(files, "files must not be null");
TFileRegistryImpl registry = THREAD_LOCAL.get();
if (registry != null) {
registry.register(files);
} else {
LOGGER.info("Trying to register files '{}', but the TFileRegistry has not been open (see TFileRegistry.open()). Ignoring registration.", files);
THREAD_LOCAL.remove();
}
}
private static final class TFileRegistryImpl implements Closeable {
private final Set<TFile> registeredFiles = Sets.newHashSet();
private TFileRegistryImpl() { }
public void register(File file) {
if (file instanceof TFile) {
TFile topLevelArchive = ((TFile)file).getTopLevelArchive();
if (topLevelArchive != null) {
registeredFiles.add(topLevelArchive);
}
}
}
public void register(Iterable<? extends File> files) {
for (File file : files) {
register(file);
}
}
@Override
public void close() {
for (TFile tFile : registeredFiles) {
try {
TVFS.sync(tFile, FsSyncOptions.SYNC);
} catch (RuntimeException | FsSyncException e) {
LOGGER.error("Error while trying to sync the truezip filesystem on '" + tFile + "'", e);
}
}
}
}
}