# HG changeset patch
# User František Kučera <franta-hg@frantovo.cz>
# Date 1414544594 -3600
# Node ID 02739f60b1ec0351c43d347eb6e53be2c696e8e4
# Parent  e62a3e4982121453512b14ea7201d7b7d546a1d2
in-fs: permissions and extended attributes

diff -r e62a3e498212 -r 02739f60b1ec java/alt2xml-in-fs/src/cz/frantovo/alt2xml/in/fs/ExtendedAttribute.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/alt2xml-in-fs/src/cz/frantovo/alt2xml/in/fs/ExtendedAttribute.java	Wed Oct 29 02:03:14 2014 +0100
@@ -0,0 +1,77 @@
+/**
+ * Alt2XML
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package cz.frantovo.alt2xml.in.fs;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+public class ExtendedAttribute {
+
+	private String key;
+	private String value;
+
+	public ExtendedAttribute(String key, String value) {
+		this.key = key;
+		this.value = value;
+	}
+
+	public ExtendedAttribute(String key, ByteBuffer value) {
+		this.key = key;
+		setValue(value);
+	}
+
+	public ExtendedAttribute() {
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public final ByteBuffer getValueBytes() {
+		return encode(getValue());
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	public final void setValue(ByteBuffer value) {
+		setValue(decode(value));
+	}
+
+	private static String decode(ByteBuffer bytes) {
+		bytes.flip();
+		return Charset.defaultCharset().decode(bytes).toString();
+	}
+
+	private static ByteBuffer encode(String text) {
+		if (text == null) {
+			return null;
+		} else {
+			return Charset.defaultCharset().encode(text);
+		}
+	}
+}
diff -r e62a3e498212 -r 02739f60b1ec java/alt2xml-in-fs/src/cz/frantovo/alt2xml/in/fs/Reader.java
--- a/java/alt2xml-in-fs/src/cz/frantovo/alt2xml/in/fs/Reader.java	Wed Oct 29 01:25:06 2014 +0100
+++ b/java/alt2xml-in-fs/src/cz/frantovo/alt2xml/in/fs/Reader.java	Wed Oct 29 02:03:14 2014 +0100
@@ -22,6 +22,16 @@
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
 import java.util.logging.Logger;
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
@@ -38,10 +48,21 @@
 	public static final String ROOT_ELEMENT = "fs";
 	public static final String DIR_ELEMENT = "dir";
 	public static final String FILE_ELEMENT = "file";
-	public static final String ROOT_ATTRIBUTE = "root";
+	public static final String XATTR_ELEMENT = "xattr";
+	public static final String PERMISSIONS_ELEMENT = "mode";
+	public static final String PERMISSIONS_U_ELEMENT = "user";
+	public static final String PERMISSIONS_G_ELEMENT = "group";
+	public static final String PERMISSIONS_O_ELEMENT = "others";
 	public static final String NAME_ATTRIBUTE = "name";
+	public static final String ABSOLUTE_PATH_ATTRIBUTE = "path";
+	public static final String SIZE_ATTRIBUTE = "size";
 	private static final Logger log = Logger.getLogger(Reader.class.getName());
 
+	/**
+	 * indentation level
+	 */
+	private int level = 0;
+
 	@Override
 	public void parse(InputSource input) throws IOException, SAXException {
 		File dir = getFile(input.getSystemId());
@@ -59,16 +80,38 @@
 		}
 	}
 
+	private String getAbsolutePath(File file) throws IOException {
+		return file.getCanonicalFile().getAbsolutePath();
+	}
+
+	private Attributes singleAttribute(String name, int value) {
+		AttributesImpl attributes = new AttributesImpl();
+		addAttribute(attributes, name, value);
+		return attributes;
+	}
+	
 	private Attributes singleAttribute(String name, String value) {
 		AttributesImpl attributes = new AttributesImpl();
-		attributes.addAttribute(null, name, name, "xs:string", value);
+		addAttribute(attributes, name, value);
 		return attributes;
 	}
 
-	private void outputStart(File root) throws SAXException {
+	private void addAttribute(AttributesImpl attributes, String name, int value) {
+		attributes.addAttribute(null, name, name, "xs:int", String.valueOf(value));
+	}
+
+	private void addAttribute(AttributesImpl attributes, String name, long value) {
+		attributes.addAttribute(null, name, name, "xs:long", String.valueOf(value));
+	}
+
+	private void addAttribute(AttributesImpl attributes, String name, String value) {
+		attributes.addAttribute(null, name, name, "xs:string", value);
+	}
+
+	private void outputStart(File root) throws SAXException, IOException {
 		contentHandler.startDocument();
 		contentHandler.lineBreak();
-		contentHandler.startElement(null, null, ROOT_ELEMENT, singleAttribute(ROOT_ATTRIBUTE, root.getAbsolutePath()));
+		contentHandler.startElement(null, null, ROOT_ELEMENT, singleAttribute(ABSOLUTE_PATH_ATTRIBUTE, getAbsolutePath(root)));
 		contentHandler.lineBreak();
 	}
 
@@ -78,22 +121,46 @@
 		contentHandler.endDocument();
 	}
 
-	private void outputFile(File file) throws SAXException {
-		contentHandler.startElement(null, null, FILE_ELEMENT, singleAttribute(NAME_ATTRIBUTE, file.getName()));
+	private void outputFile(File file) throws SAXException, IOException {
+		level++;
+
+		AttributesImpl attributes = new AttributesImpl();
+		addAttribute(attributes, NAME_ATTRIBUTE, file.getName());
+		addAttribute(attributes, ABSOLUTE_PATH_ATTRIBUTE, getAbsolutePath(file));
+		addAttribute(attributes, SIZE_ATTRIBUTE, file.length());
+
+		contentHandler.indentation(level);
+		contentHandler.startElement(null, null, FILE_ELEMENT, attributes);
 		contentHandler.lineBreak();
 
-		//contentHandler.characters(file.getName());
+		outputPermissions(file.toPath());
+		outputExtendedAttributes(file);
 
+		contentHandler.indentation(level);
 		contentHandler.endElement(null, null, FILE_ELEMENT);
 		contentHandler.lineBreak();
+
+		level--;
 	}
 
-	private void outputDir(File dir) throws SAXException {
+	private void outputDir(File dir) throws SAXException, IOException {
+		level++;
 
-		contentHandler.startElement(null, null, DIR_ELEMENT, singleAttribute(NAME_ATTRIBUTE, dir.getName()));
+		final File[] children = dir.listFiles();
+
+		AttributesImpl attributes = new AttributesImpl();
+		addAttribute(attributes, NAME_ATTRIBUTE, dir.getName());
+		addAttribute(attributes, ABSOLUTE_PATH_ATTRIBUTE, getAbsolutePath(dir));
+		addAttribute(attributes, SIZE_ATTRIBUTE, children.length);
+
+		contentHandler.indentation(level);
+		contentHandler.startElement(null, null, DIR_ELEMENT, attributes);
 		contentHandler.lineBreak();
 
-		for (File file : dir.listFiles()) {
+		outputPermissions(dir.toPath());
+		outputExtendedAttributes(dir);
+
+		for (File file : children) {
 			if (file.isDirectory()) {
 				outputDir(file);
 			} else {
@@ -101,7 +168,112 @@
 			}
 		}
 
+		contentHandler.indentation(level);
 		contentHandler.endElement(null, null, DIR_ELEMENT);
 		contentHandler.lineBreak();
+
+		level--;
+	}
+
+	private String encodeElementName(String originalName) {
+		/**
+		 * TODO: encode and/or skip invalid characters
+		 */
+		return originalName;
+	}
+
+	private void outputPermissions(Path path) throws IOException, SAXException {
+		level++;
+		Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, LinkOption.NOFOLLOW_LINKS);
+
+		contentHandler.indentation(level);
+		contentHandler.startElement(null, PERMISSIONS_ELEMENT, PERMISSIONS_ELEMENT, singleAttribute("octal", getOctal(permissions)));
+		contentHandler.lineBreak();
+
+		level++;
+
+		outputPermission("owner", permissions.contains(PosixFilePermission.OWNER_READ), permissions.contains(PosixFilePermission.OWNER_WRITE), permissions.contains(PosixFilePermission.OWNER_EXECUTE));
+		outputPermission("group", permissions.contains(PosixFilePermission.GROUP_READ), permissions.contains(PosixFilePermission.GROUP_WRITE), permissions.contains(PosixFilePermission.GROUP_EXECUTE));
+		outputPermission("others", permissions.contains(PosixFilePermission.OTHERS_READ), permissions.contains(PosixFilePermission.OTHERS_WRITE), permissions.contains(PosixFilePermission.OTHERS_EXECUTE));
+
+		level--;
+
+		contentHandler.indentation(level);
+		contentHandler.endElement(null, PERMISSIONS_ELEMENT, PERMISSIONS_ELEMENT);
+		contentHandler.lineBreak();
+
+		level--;
+	}
+
+	private int getOctal(Set<PosixFilePermission> permissions) {
+		int octal = 0;
+		
+		octal = octal + 100 * (permissions.contains(PosixFilePermission.OWNER_READ) ? 4 : 0);
+		octal = octal + 100 * (permissions.contains(PosixFilePermission.OWNER_WRITE) ? 2 : 0);
+		octal = octal + 100 * (permissions.contains(PosixFilePermission.OWNER_EXECUTE) ? 1 : 0);
+		
+		octal = octal + 10 * (permissions.contains(PosixFilePermission.GROUP_READ) ? 4 : 0);
+		octal = octal + 10 * (permissions.contains(PosixFilePermission.GROUP_WRITE) ? 2 : 0);
+		octal = octal + 10 * (permissions.contains(PosixFilePermission.GROUP_EXECUTE) ? 1 : 0);
+		
+		octal = octal + 1 * (permissions.contains(PosixFilePermission.OTHERS_READ) ? 4 : 0);
+		octal = octal + 1 * (permissions.contains(PosixFilePermission.OTHERS_WRITE) ? 2 : 0);
+		octal = octal + 1 * (permissions.contains(PosixFilePermission.OTHERS_EXECUTE) ? 1 : 0);
+
+		return octal;
+	}
+
+	private void outputPermission(String name, boolean read, boolean write, boolean execute) throws SAXException {
+		contentHandler.indentation(level);
+		AttributesImpl attributes = new AttributesImpl();
+		attributes.addAttribute(null, "read", "read", "xs:boolean", String.valueOf(read));
+		attributes.addAttribute(null, "write", "write", "xs:boolean", String.valueOf(write));
+		attributes.addAttribute(null, "execute", "execute", "xs:boolean", String.valueOf(execute));
+		contentHandler.startElement(null, name, name, attributes);
+		contentHandler.endElement(null, name, name);
+		contentHandler.lineBreak();
+	}
+
+	private void outputExtendedAttributes(File file) throws IOException, SAXException {
+		level++;
+
+		final List<ExtendedAttribute> extendedAttributes = getExtendedAttributes(file);
+
+		if (!extendedAttributes.isEmpty()) {
+			contentHandler.indentation(level);
+			contentHandler.startElement(null, XATTR_ELEMENT, XATTR_ELEMENT, null);
+			contentHandler.lineBreak();
+			level++;
+			for (ExtendedAttribute ea : extendedAttributes) {
+				contentHandler.indentation(level);
+				String elementName = encodeElementName(ea.getKey());
+				contentHandler.textElement(ea.getValue(), null, elementName, elementName, null);
+				contentHandler.lineBreak();
+			}
+			level--;
+			contentHandler.indentation(level);
+			contentHandler.endElement(null, XATTR_ELEMENT, XATTR_ELEMENT);
+			contentHandler.lineBreak();
+		}
+
+		level--;
+	}
+
+	private List<ExtendedAttribute> getExtendedAttributes(File file) throws IOException {
+
+		List<ExtendedAttribute> l = new ArrayList<>();
+
+		Path path = file.toPath();
+		FileSystemProvider provider = path.getFileSystem().provider();
+		UserDefinedFileAttributeView attributes = provider.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+
+		for (String jménoAtributu : attributes.list()) {
+			ByteBuffer hodnotaAtributu = ByteBuffer.allocate(attributes.size(jménoAtributu));
+			attributes.read(jménoAtributu, hodnotaAtributu);
+			l.add(new ExtendedAttribute(jménoAtributu, hodnotaAtributu));
+		}
+
+		return l;
+
 	}
 }