package org.dcache.pool.repository.meta.file; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URI; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.Collection; import diskCacheV111.util.CacheException; import diskCacheV111.util.DiskErrorCacheException; import diskCacheV111.util.PnfsId; import diskCacheV111.vehicles.StorageInfo; import diskCacheV111.vehicles.StorageInfos; import org.dcache.namespace.FileAttribute; import org.dcache.pool.movers.IoMode; import org.dcache.pool.repository.ReplicaState; import org.dcache.pool.repository.FileStore; import org.dcache.pool.repository.ReplicaRecord; import org.dcache.pool.repository.RepositoryChannel; import org.dcache.pool.repository.StickyRecord; import org.dcache.pool.repository.v3.entry.CacheRepositoryEntryState; import org.dcache.vehicles.FileAttributes; public class CacheRepositoryEntryImpl implements ReplicaRecord, ReplicaRecord.UpdatableRecord { private static final Logger LOGGER = LoggerFactory.getLogger(CacheRepositoryEntryImpl.class); private final CacheRepositoryEntryState _state; private final PnfsId _pnfsId; private int _linkCount; private StorageInfo _storageInfo; private long _creationTime = System.currentTimeMillis(); private long _lastAccess; private long _size; /** * control file */ private final Path _controlFile; /** * serialized storage info file */ private final Path _siFile; /** * File store used for data file. */ private final FileStore _fileStore; public CacheRepositoryEntryImpl(PnfsId pnfsId, Path controlFile, FileStore fileStore, Path siFile) throws IOException { _pnfsId = pnfsId; _controlFile = controlFile; _siFile = siFile; _fileStore = fileStore; _state = new CacheRepositoryEntryState(_controlFile); try { _storageInfo = readStorageInfo(siFile); _creationTime = Files.getLastModifiedTime(_siFile).toMillis(); } catch (FileNotFoundException | NoSuchFileException fnf) { /* * it's not an error state. */ } try { BasicFileAttributes attributes = _fileStore .getFileAttributeView(pnfsId) .readAttributes(); _lastAccess = attributes.lastModifiedTime().toMillis(); _size = attributes.size(); } catch (FileNotFoundException | NoSuchFileException fnf) { /* * it's not an error state. */ } if (_lastAccess == 0) { _lastAccess = _creationTime; } } @Override public synchronized int incrementLinkCount() { _linkCount++; return _linkCount; } @Override public synchronized int decrementLinkCount() { if (_linkCount <= 0) { throw new IllegalStateException("Link count is already zero"); } _linkCount--; return _linkCount; } @Override public synchronized int getLinkCount() { return _linkCount; } @Override public synchronized long getCreationTime() { return _creationTime; } @Override public synchronized URI getReplicaUri() { return _fileStore.get(_pnfsId); } @Override public RepositoryChannel openChannel(IoMode mode) throws IOException { return _fileStore.openDataChannel(_pnfsId, mode); } @Override public synchronized long getLastAccessTime() { return _lastAccess; } @Override public synchronized void setLastAccessTime(long time) throws CacheException { try { _fileStore .getFileAttributeView(_pnfsId) .setTimes(FileTime.fromMillis(time), null, null); } catch (IOException e) { throw new DiskErrorCacheException("Failed to set modification time: " + _pnfsId, e); } _lastAccess = System.currentTimeMillis(); } @Override public synchronized PnfsId getPnfsId() { return _pnfsId; } @Override public synchronized long getReplicaSize() { try { return _state.getState().isMutable() ? _fileStore.getFileAttributeView(_pnfsId).readAttributes().size() : _size; } catch (NoSuchFileException e) { return 0; } catch (IOException e) { LOGGER.error("Failed to read file size: " + e); return 0; } } @Override public synchronized boolean isSticky() { return _state.isSticky(); } @Override public synchronized ReplicaState getState() { return _state.getState(); } @Override public synchronized Void setState(ReplicaState state) throws CacheException { try { if (_state.getState().isMutable() && !state.isMutable()) { try { _size = _fileStore.getFileAttributeView(_pnfsId).readAttributes().size(); } catch (NoSuchFileException e) { _size = 0; } } _state.setState(state); } catch (IOException e) { throw new DiskErrorCacheException(e.getMessage(), e); } return null; } @Override public synchronized Collection<StickyRecord> removeExpiredStickyFlags() throws CacheException { try { return _state.removeExpiredStickyFlags(); } catch (IOException e) { throw new DiskErrorCacheException(e.getMessage(), e); } } @Override public boolean setSticky(String owner, long expire, boolean overwrite) throws CacheException { try { if (_state.setSticky(owner, expire, overwrite)) { return true; } return false; } catch (IllegalStateException | IOException e) { throw new DiskErrorCacheException(e.getMessage(), e); } } @Override public synchronized FileAttributes getFileAttributes() { FileAttributes attributes = FileAttributes.ofPnfsId(_pnfsId); StorageInfo storageInfo = getStorageInfo(); if (storageInfo != null) { StorageInfos.injectInto(storageInfo, attributes); } return attributes; } @Override public Void setFileAttributes(FileAttributes attributes) throws CacheException { try { if (attributes.isDefined(FileAttribute.STORAGEINFO)) { setStorageInfo(StorageInfos.extractFrom(attributes)); } else { setStorageInfo(null); } } catch (IOException e) { throw new DiskErrorCacheException(_pnfsId + " " + e.getMessage(), e); } return null; } private synchronized StorageInfo getStorageInfo() { return _storageInfo; } private synchronized void setStorageInfo(StorageInfo storageInfo) throws IOException { Path siFileTemp = Files.createTempFile(_siFile.getParent(), _siFile.getFileName().toString(), null); try { try (ObjectOutputStream objectOut = new ObjectOutputStream(new BufferedOutputStream(Files.newOutputStream(siFileTemp)))) { objectOut.writeObject(storageInfo); } Files.move(siFileTemp, _siFile, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); } finally { Files.deleteIfExists(siFileTemp); } _storageInfo = storageInfo; } private static StorageInfo readStorageInfo(Path objIn) throws IOException { try { try (ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(Files.newInputStream(objIn)))) { return (StorageInfo) in.readObject(); } } catch (Throwable t) { LOGGER.debug("Failed to read {}: {}", objIn, t.toString()); } return null; } @Override public synchronized Collection<StickyRecord> stickyRecords() { return _state.stickyRecords(); } @Override public synchronized <T> T update(Update<T> update) throws CacheException { return update.apply(this); } }