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@413: 		// TODO post-1.0 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@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: }