tikhomirov@287: /* tikhomirov@413: * Copyright (c) 2011-2012 TMate Software Ltd tikhomirov@287: * tikhomirov@287: * This program is free software; you can redistribute it and/or modify tikhomirov@287: * it under the terms of the GNU General Public License as published by tikhomirov@287: * the Free Software Foundation; version 2 of the License. tikhomirov@287: * tikhomirov@287: * This program is distributed in the hope that it will be useful, tikhomirov@287: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@287: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@287: * GNU General Public License for more details. tikhomirov@287: * tikhomirov@287: * For information on how to redistribute this software under tikhomirov@287: * the terms of a license other than GNU General Public License tikhomirov@287: * contact TMate Software at support@hg4j.com tikhomirov@287: */ tikhomirov@287: package org.tmatesoft.hg.util; tikhomirov@287: tikhomirov@456: import static org.tmatesoft.hg.util.LogFacility.Severity.Info; tikhomirov@456: tikhomirov@287: import java.io.File; tikhomirov@287: import java.io.FileInputStream; tikhomirov@287: import java.io.FileNotFoundException; tikhomirov@287: import java.io.IOException; tikhomirov@287: import java.nio.ByteBuffer; tikhomirov@413: import java.nio.channels.ClosedChannelException; tikhomirov@287: import java.nio.channels.ReadableByteChannel; tikhomirov@287: tikhomirov@425: import org.tmatesoft.hg.core.SessionContext; tikhomirov@295: import org.tmatesoft.hg.internal.StreamLogFacility; tikhomirov@295: tikhomirov@287: /** tikhomirov@287: * tikhomirov@287: * @author Artem Tikhomirov tikhomirov@287: * @author TMate Software Ltd. tikhomirov@287: */ tikhomirov@287: public class RegularFileInfo implements FileInfo { tikhomirov@413: private final boolean supportsExec, supportsLink; tikhomirov@413: private final RegularFileStats fileFlagsHelper; // null if both supportsLink and supportExec are false tikhomirov@287: private File file; tikhomirov@287: tikhomirov@425: public RegularFileInfo(SessionContext ctx) { tikhomirov@425: this(ctx, false, false); tikhomirov@413: } tikhomirov@425: public RegularFileInfo(SessionContext ctx, boolean supportExecFlag, boolean supportSymlink) { tikhomirov@413: supportsLink = supportSymlink; tikhomirov@413: supportsExec = supportExecFlag; tikhomirov@413: if (supportSymlink || supportExecFlag) { tikhomirov@425: fileFlagsHelper = new RegularFileStats(ctx); tikhomirov@413: } else { tikhomirov@413: fileFlagsHelper = null; tikhomirov@413: } tikhomirov@287: } tikhomirov@287: tikhomirov@287: public void init(File f) { tikhomirov@287: file = f; tikhomirov@413: if (fileFlagsHelper != null) { tikhomirov@413: fileFlagsHelper.init(file); tikhomirov@413: } tikhomirov@287: } tikhomirov@287: tikhomirov@287: public boolean exists() { tikhomirov@413: // java.io.File for symlinks without proper target says it doesn't exist. tikhomirov@413: // since we found this symlink in directory listing, it's safe to say it exists just based on the fact it's link tikhomirov@413: return isSymlink() || (file.canRead() && file.isFile()); tikhomirov@287: } tikhomirov@287: tikhomirov@287: public int lastModified() { tikhomirov@619: // TODO [post-1.1] for symlinks, this returns incorrect mtime of the target file, not that of link itself tikhomirov@413: // Besides, timestame if link points to non-existing file is 0. tikhomirov@413: // However, it result only in slowdown in WCStatusCollector, as it need to perform additional content check tikhomirov@287: return (int) (file.lastModified() / 1000); tikhomirov@287: } tikhomirov@287: tikhomirov@287: public long length() { tikhomirov@413: if (isSymlink()) { tikhomirov@413: return getLinkTargetBytes().length; tikhomirov@413: } tikhomirov@287: return file.length(); tikhomirov@287: } tikhomirov@287: tikhomirov@287: public ReadableByteChannel newInputChannel() { tikhomirov@287: try { tikhomirov@413: if (isSymlink()) { tikhomirov@413: return new ByteArrayReadableChannel(getLinkTargetBytes()); tikhomirov@413: } else { tikhomirov@619: // TODO [2.0 API break] might be good idea replace channel with smth tikhomirov@619: // else, to ensure #close() disposes FileDescriptor. Now tikhomirov@619: // FD has usage count of two (new FileInputStream + getChannel), tikhomirov@619: // and FileChannel#close decrements only 1, second has to wait FIS#finalize() tikhomirov@413: return new FileInputStream(file).getChannel(); tikhomirov@413: } tikhomirov@287: } catch (FileNotFoundException ex) { tikhomirov@456: StreamLogFacility.newDefault().dump(getClass(), Info, ex, null); tikhomirov@287: // shall not happen, provided this class is used correctly tikhomirov@413: return new ByteArrayReadableChannel(null); tikhomirov@287: } tikhomirov@287: } tikhomirov@287: tikhomirov@413: public boolean isExecutable() { tikhomirov@413: return supportsExec && fileFlagsHelper.isExecutable(); tikhomirov@413: } tikhomirov@413: tikhomirov@413: public boolean isSymlink() { tikhomirov@413: return supportsLink && fileFlagsHelper.isSymlink(); tikhomirov@413: } tikhomirov@413: tikhomirov@476: @Override tikhomirov@476: public String toString() { tikhomirov@476: char t = exists() ? (isExecutable() ? '*' : (isSymlink() ? '@' : '-')) : '!'; tikhomirov@476: return String.format("RegularFileInfo[%s %c]", file.getPath(), t); tikhomirov@476: } tikhomirov@476: tikhomirov@413: private byte[] getLinkTargetBytes() { tikhomirov@413: assert isSymlink(); tikhomirov@413: // no idea what encoding Mercurial uses for link targets, assume platform native is ok tikhomirov@413: return fileFlagsHelper.getSymlinkTarget().getBytes(); tikhomirov@413: } tikhomirov@413: tikhomirov@413: tikhomirov@413: private static class ByteArrayReadableChannel implements ReadableByteChannel { tikhomirov@413: private final byte[] data; tikhomirov@413: private boolean closed = false; // initially open tikhomirov@413: private int firstAvailIndex = 0; tikhomirov@413: tikhomirov@413: ByteArrayReadableChannel(byte[] dataToStream) { tikhomirov@413: data = dataToStream; tikhomirov@413: } tikhomirov@413: tikhomirov@413: public boolean isOpen() { tikhomirov@413: return !closed; tikhomirov@413: } tikhomirov@413: tikhomirov@413: public void close() throws IOException { tikhomirov@413: closed = true; tikhomirov@413: } tikhomirov@413: tikhomirov@413: public int read(ByteBuffer dst) throws IOException { tikhomirov@413: if (closed) { tikhomirov@413: throw new ClosedChannelException(); tikhomirov@413: } tikhomirov@413: int remainingBytes = data.length - firstAvailIndex; tikhomirov@413: if (data == null || remainingBytes == 0) { tikhomirov@413: // EOF right away tikhomirov@413: return -1; tikhomirov@413: } tikhomirov@413: int x = Math.min(dst.remaining(), remainingBytes); tikhomirov@413: for (int i = firstAvailIndex, lim = firstAvailIndex + x; i < lim; i++) { tikhomirov@413: dst.put(data[i]); tikhomirov@413: } tikhomirov@413: firstAvailIndex += x; tikhomirov@413: return x; tikhomirov@413: } tikhomirov@413: } tikhomirov@287: }