comparison 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
comparison
equal deleted inserted replaced
406:d56ea1a2537a 413:7f27122011c3
1 /* 1 /*
2 * Copyright (c) 2011 TMate Software Ltd 2 * Copyright (c) 2011-2012 TMate Software Ltd
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify 4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by 5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License. 6 * the Free Software Foundation; version 2 of the License.
7 * 7 *
19 import java.io.File; 19 import java.io.File;
20 import java.io.FileInputStream; 20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException; 21 import java.io.FileNotFoundException;
22 import java.io.IOException; 22 import java.io.IOException;
23 import java.nio.ByteBuffer; 23 import java.nio.ByteBuffer;
24 import java.nio.channels.ClosedChannelException;
24 import java.nio.channels.ReadableByteChannel; 25 import java.nio.channels.ReadableByteChannel;
25 26
26 import org.tmatesoft.hg.internal.StreamLogFacility; 27 import org.tmatesoft.hg.internal.StreamLogFacility;
27 28
28 /** 29 /**
29 * 30 *
30 * @author Artem Tikhomirov 31 * @author Artem Tikhomirov
31 * @author TMate Software Ltd. 32 * @author TMate Software Ltd.
32 */ 33 */
33 public class RegularFileInfo implements FileInfo { 34 public class RegularFileInfo implements FileInfo {
35 private final boolean supportsExec, supportsLink;
36 private final RegularFileStats fileFlagsHelper; // null if both supportsLink and supportExec are false
34 private File file; 37 private File file;
35 38
36 public RegularFileInfo() { 39 public RegularFileInfo() {
40 this(false, false);
41 }
42 public RegularFileInfo(boolean supportExecFlag, boolean supportSymlink) {
43 supportsLink = supportSymlink;
44 supportsExec = supportExecFlag;
45 if (supportSymlink || supportExecFlag) {
46 fileFlagsHelper = new RegularFileStats();
47 } else {
48 fileFlagsHelper = null;
49 }
37 } 50 }
38 51
39 public void init(File f) { 52 public void init(File f) {
40 file = f; 53 file = f;
54 if (fileFlagsHelper != null) {
55 fileFlagsHelper.init(file);
56 }
41 } 57 }
42 58
43 public boolean exists() { 59 public boolean exists() {
44 return file.canRead() && file.isFile(); 60 // java.io.File for symlinks without proper target says it doesn't exist.
61 // since we found this symlink in directory listing, it's safe to say it exists just based on the fact it's link
62 return isSymlink() || (file.canRead() && file.isFile());
45 } 63 }
46 64
47 public int lastModified() { 65 public int lastModified() {
66 // TODO post-1.0 for symlinks, this returns incorrect mtime of the target file, not that of link itself
67 // Besides, timestame if link points to non-existing file is 0.
68 // However, it result only in slowdown in WCStatusCollector, as it need to perform additional content check
48 return (int) (file.lastModified() / 1000); 69 return (int) (file.lastModified() / 1000);
49 } 70 }
50 71
51 public long length() { 72 public long length() {
73 if (isSymlink()) {
74 return getLinkTargetBytes().length;
75 }
52 return file.length(); 76 return file.length();
53 } 77 }
54 78
55 public ReadableByteChannel newInputChannel() { 79 public ReadableByteChannel newInputChannel() {
56 try { 80 try {
57 return new FileInputStream(file).getChannel(); 81 if (isSymlink()) {
82 return new ByteArrayReadableChannel(getLinkTargetBytes());
83 } else {
84 return new FileInputStream(file).getChannel();
85 }
58 } catch (FileNotFoundException ex) { 86 } catch (FileNotFoundException ex) {
59 StreamLogFacility.newDefault().debug(getClass(), ex, null); 87 StreamLogFacility.newDefault().debug(getClass(), ex, null);
60 // shall not happen, provided this class is used correctly 88 // shall not happen, provided this class is used correctly
61 return new ReadableByteChannel() { 89 return new ByteArrayReadableChannel(null);
62
63 public boolean isOpen() {
64 return true;
65 }
66
67 public void close() throws IOException {
68 }
69
70 public int read(ByteBuffer dst) throws IOException {
71 // EOF right away
72 return -1;
73 }
74 };
75 } 90 }
76 } 91 }
77 92
93 public boolean isExecutable() {
94 return supportsExec && fileFlagsHelper.isExecutable();
95 }
96
97 public boolean isSymlink() {
98 return supportsLink && fileFlagsHelper.isSymlink();
99 }
100
101 private byte[] getLinkTargetBytes() {
102 assert isSymlink();
103 // no idea what encoding Mercurial uses for link targets, assume platform native is ok
104 return fileFlagsHelper.getSymlinkTarget().getBytes();
105 }
106
107
108 private static class ByteArrayReadableChannel implements ReadableByteChannel {
109 private final byte[] data;
110 private boolean closed = false; // initially open
111 private int firstAvailIndex = 0;
112
113 ByteArrayReadableChannel(byte[] dataToStream) {
114 data = dataToStream;
115 }
116
117 public boolean isOpen() {
118 return !closed;
119 }
120
121 public void close() throws IOException {
122 closed = true;
123 }
124
125 public int read(ByteBuffer dst) throws IOException {
126 if (closed) {
127 throw new ClosedChannelException();
128 }
129 int remainingBytes = data.length - firstAvailIndex;
130 if (data == null || remainingBytes == 0) {
131 // EOF right away
132 return -1;
133 }
134 int x = Math.min(dst.remaining(), remainingBytes);
135 for (int i = firstAvailIndex, lim = firstAvailIndex + x; i < lim; i++) {
136 dst.put(data[i]);
137 }
138 firstAvailIndex += x;
139 return x;
140 }
141 }
78 } 142 }