diff src/org/tmatesoft/hg/util/RegularFileInfo.java @ 413:7f27122011c3

Support and respect for symbolic links and executable flag, with /bin/ls backed implementation to discover these
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 21 Mar 2012 20:40:28 +0100
parents 981f9f50bb6c
children 48f993aa2f41
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/util/RegularFileInfo.java	Thu Mar 15 16:51:46 2012 +0100
+++ b/src/org/tmatesoft/hg/util/RegularFileInfo.java	Wed Mar 21 20:40:28 2012 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011 TMate Software Ltd
+ * Copyright (c) 2011-2012 TMate Software Ltd
  *  
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.ReadableByteChannel;
 
 import org.tmatesoft.hg.internal.StreamLogFacility;
@@ -31,48 +32,111 @@
  * @author TMate Software Ltd.
  */
 public class RegularFileInfo implements FileInfo {
+	private final boolean supportsExec, supportsLink;
+	private final RegularFileStats fileFlagsHelper; // null if both supportsLink and supportExec are false
 	private File file;
 	
 	public RegularFileInfo() {
+		this(false, false);
+	}
+	public RegularFileInfo(boolean supportExecFlag, boolean supportSymlink) {
+		supportsLink = supportSymlink;
+		supportsExec = supportExecFlag;
+		if (supportSymlink || supportExecFlag) {
+			fileFlagsHelper = new RegularFileStats();
+		} else  {
+			fileFlagsHelper = null;
+		}
 	}
 	
 	public void init(File f) {
 		file = f;
+		if (fileFlagsHelper != null) {
+			fileFlagsHelper.init(file);
+		}
 	}
 	
 	public boolean exists() {
-		return file.canRead() && file.isFile();
+		// java.io.File for symlinks without proper target says it doesn't exist.
+		// since we found this symlink in directory listing, it's safe to say it exists just based on the fact it's link
+		return isSymlink() || (file.canRead() && file.isFile());
 	}
 
 	public int lastModified() {
+		// TODO post-1.0 for symlinks, this returns incorrect mtime of the target file, not that of link itself
+		// Besides, timestame if link points to non-existing file is 0.
+		// However, it result only in slowdown in WCStatusCollector, as it need to perform additional content check
 		return (int) (file.lastModified() / 1000);
 	}
 
 	public long length() {
+		if (isSymlink()) {
+			return getLinkTargetBytes().length;
+		}
 		return file.length();
 	}
 
 	public ReadableByteChannel newInputChannel() {
 		try {
-			return new FileInputStream(file).getChannel();
+			if (isSymlink()) {
+				return new ByteArrayReadableChannel(getLinkTargetBytes());
+			} else {
+				return new FileInputStream(file).getChannel();
+			}
 		} catch (FileNotFoundException ex) {
 			StreamLogFacility.newDefault().debug(getClass(), ex, null);
 			// shall not happen, provided this class is used correctly
-			return new ReadableByteChannel() {
-				
-				public boolean isOpen() {
-					return true;
-				}
-				
-				public void close() throws IOException {
-				}
-				
-				public int read(ByteBuffer dst) throws IOException {
-					// EOF right away
-					return -1;
-				}
-			};
+			return new ByteArrayReadableChannel(null);
 		}
 	}
 
+	public boolean isExecutable() {
+		return supportsExec && fileFlagsHelper.isExecutable();
+	}
+	
+	public boolean isSymlink() {
+		return supportsLink && fileFlagsHelper.isSymlink();
+	}
+	
+	private byte[] getLinkTargetBytes() {
+		assert isSymlink();
+		// no idea what encoding Mercurial uses for link targets, assume platform native is ok
+		return fileFlagsHelper.getSymlinkTarget().getBytes();
+	}
+
+
+	private static class ByteArrayReadableChannel implements ReadableByteChannel {
+		private final byte[] data;
+		private boolean closed = false; // initially open
+		private int firstAvailIndex = 0;
+		
+		ByteArrayReadableChannel(byte[] dataToStream) {
+			data = dataToStream;
+		}
+
+		public boolean isOpen() {
+			return !closed;
+		}
+
+		public void close() throws IOException {
+			closed = true;
+		}
+
+		public int read(ByteBuffer dst) throws IOException {
+			if (closed) {
+				throw new ClosedChannelException();
+			}
+			int remainingBytes = data.length - firstAvailIndex;
+			if (data == null || remainingBytes == 0) {
+				// EOF right away
+				return -1;
+			}
+			int x = Math.min(dst.remaining(), remainingBytes);
+			for (int i = firstAvailIndex, lim = firstAvailIndex + x; i < lim; i++) {
+				dst.put(data[i]);
+			}
+			firstAvailIndex += x;
+			return x;
+		}
+	}
 }