diff --git a/build.xml b/build.xml index 4d7d6ccb..76a0ecce 100644 --- a/build.xml +++ b/build.xml @@ -157,7 +157,7 @@ - + @@ -181,10 +181,6 @@ - - - - @@ -224,42 +220,42 @@ - + - + - + - + - + diff --git a/lib/native/mac-x86_64/libxattrj.dylib b/lib/native/mac-x86_64/libxattrj.dylib deleted file mode 100644 index 51687e40..00000000 Binary files a/lib/native/mac-x86_64/libxattrj.dylib and /dev/null differ diff --git a/lib/xattrj.jar b/lib/xattrj.jar deleted file mode 100644 index 12ed6d4a..00000000 Binary files a/lib/xattrj.jar and /dev/null differ diff --git a/source/net/filebot/MetaAttributeView.java b/source/net/filebot/MetaAttributeView.java index 9d69f64d..26c0a741 100644 --- a/source/net/filebot/MetaAttributeView.java +++ b/source/net/filebot/MetaAttributeView.java @@ -1,6 +1,7 @@ package net.filebot; -import static java.nio.file.Files.*; +import static java.nio.file.Files.isSymbolicLink; +import static java.nio.file.Files.readSymbolicLink; import java.io.File; import java.io.IOException; @@ -12,31 +13,20 @@ import java.nio.file.attribute.UserDefinedFileAttributeView; import java.text.Normalizer; import java.text.Normalizer.Form; import java.util.AbstractMap; -import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import org.securityvision.xattrj.Xattrj; +import net.filebot.mac.xattr.XattrUtil; import com.sun.jna.Platform; public class MetaAttributeView extends AbstractMap { - // UserDefinedFileAttributeView (for Windows and Linux) OR 3rd party Xattrj (for Mac) because UserDefinedFileAttributeView is not supported (Oracle Java 7/8) + // UserDefinedFileAttributeView (for Windows and Linux) OR our own xattr.h JNA wrapper via MacXattrView (for Mac) because UserDefinedFileAttributeView is not supported (Oracle Java 7/8) private Object xattr; - private File file; private Charset encoding; - private static Xattrj MacXattrSupport; - - private static synchronized Xattrj getMacXattrSupport() throws Throwable { - if (MacXattrSupport == null) { - MacXattrSupport = new Xattrj(); - } - return MacXattrSupport; - } - public MetaAttributeView(File file) throws IOException { Path path = file.getCanonicalFile().toPath(); while (isSymbolicLink(path)) { @@ -49,25 +39,14 @@ public class MetaAttributeView extends AbstractMap { xattr = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); - // try 3rd party Xattrj library if (xattr == null) { if (Platform.isMac()) { - // Xattrj for Mac - try { - xattr = getMacXattrSupport(); - } catch (Throwable e) { - throw new IOException("Unable to load library: xattrj", e); - } - - // MacOS filesystem may require NFD unicode decomposition - this.file = new File(Normalizer.normalize(path.toFile().getAbsolutePath(), Form.NFD)); - this.encoding = Charset.forName("UTF-8"); + xattr = new MacXattrView(path); } else { throw new IOException("UserDefinedFileAttributeView is not supported"); } } else { // UserDefinedFileAttributeView - this.file = path.toFile(); this.encoding = Charset.forName("UTF-8"); } } @@ -84,9 +63,9 @@ public class MetaAttributeView extends AbstractMap { return encoding.decode(buffer).toString(); } - if (xattr instanceof Xattrj) { - Xattrj macXattr = (Xattrj) xattr; - return macXattr.readAttribute(file, key.toString()); + if (xattr instanceof MacXattrView) { + MacXattrView macXattr = (MacXattrView) xattr; + return macXattr.read(key.toString()); } } catch (Exception e) { // ignore @@ -107,12 +86,12 @@ public class MetaAttributeView extends AbstractMap { } } - if (xattr instanceof Xattrj) { - Xattrj macXattr = (Xattrj) xattr; + if (xattr instanceof MacXattrView) { + MacXattrView macXattr = (MacXattrView) xattr; if (value == null || value.isEmpty()) { - macXattr.removeAttribute(file, key); + macXattr.delete(key); } else { - macXattr.writeAttribute(file, key, value); + macXattr.write(key, value); } } } catch (Exception e) { @@ -128,9 +107,9 @@ public class MetaAttributeView extends AbstractMap { return attributeView.list(); } - if (xattr instanceof Xattrj) { - Xattrj macXattr = (Xattrj) xattr; - return Arrays.asList(macXattr.listAttributes(file)); + if (xattr instanceof MacXattrView) { + MacXattrView macXattr = (MacXattrView) xattr; + return macXattr.list(); } return null; @@ -189,4 +168,30 @@ public class MetaAttributeView extends AbstractMap { } } + private static class MacXattrView { + + private final String path; + + public MacXattrView(Path path) { + // MacOS filesystem may require NFD unicode decomposition + this.path = Normalizer.normalize(path.toFile().getAbsolutePath(), Form.NFD); + } + + public List list() { + return XattrUtil.listXAttr(path); + } + + public String read(String key) { + return XattrUtil.getXAttr(path, key); + } + + public void write(String key, String value) { + XattrUtil.setXAttr(path, key, value); + } + + public void delete(String key) { + XattrUtil.removeXAttr(path, key); + } + } + } diff --git a/source/net/filebot/mac/xattr/XAttr.java b/source/net/filebot/mac/xattr/XAttr.java new file mode 100644 index 00000000..73dead39 --- /dev/null +++ b/source/net/filebot/mac/xattr/XAttr.java @@ -0,0 +1,51 @@ +/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ +package net.filebot.mac.xattr; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +/** + * JNA wrapper for + * + */ +interface XAttr extends Library { + + // load from current image + XAttr INSTANCE = (XAttr) Native.loadLibrary(null, XAttr.class); + + // see /usr/include/sys/xattr.h + int XATTR_NOFOLLOW = 0x0001; + int XATTR_CREATE = 0x0002; + int XATTR_REPLACE = 0x0004; + int XATTR_NOSECURITY = 0x0008; + int XATTR_NODEFAULT = 0x0010; + int XATTR_SHOWCOMPRESSION = 0x0020; + int XATTR_MAXNAMELEN = 127; + String XATTR_FINDERINFO_NAME = "com.apple.FinderInfo"; + String XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork"; + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html + long getxattr(String path, String name, Pointer value, long size, int position, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setxattr.2.html + int setxattr(String path, String name, Pointer value, long size, int position, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/removexattr.2.html + int removexattr(String path, String name, int options); + + // see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/listxattr.2.html + long listxattr(String path, Pointer namebuff, long size, int options); + +} diff --git a/source/net/filebot/mac/xattr/XattrUtil.java b/source/net/filebot/mac/xattr/XattrUtil.java new file mode 100644 index 00000000..cbfa8f00 --- /dev/null +++ b/source/net/filebot/mac/xattr/XattrUtil.java @@ -0,0 +1,100 @@ +/* Copyright (c) 2014 Reinhard Pointner, All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + */ +package net.filebot.mac.xattr; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; + +public class XattrUtil { + + public static List listXAttr(String path) { + // get required buffer size + long bufferLength = XAttr.INSTANCE.listxattr(path, Pointer.NULL, 0, 0); + + if (bufferLength < 0) + return null; + + if (bufferLength == 0) + return new ArrayList(0); + + Memory valueBuffer = new Memory(bufferLength); + long valueLength = XAttr.INSTANCE.listxattr(path, valueBuffer, bufferLength, 0); + + if (valueLength < 0) + return null; + + return decodeStringSequence(valueBuffer.getByteBuffer(0, valueLength)); + } + + public static String getXAttr(String path, String name) { + // get required buffer size + long bufferLength = XAttr.INSTANCE.getxattr(path, name, Pointer.NULL, 0, 0, 0); + + if (bufferLength < 0) + return null; + + Memory valueBuffer = new Memory(bufferLength); + long valueLength = XAttr.INSTANCE.getxattr(path, name, valueBuffer, bufferLength, 0, 0); + + if (valueLength < 0) + return null; + + return decodeString(valueBuffer.getByteBuffer(0, valueLength - 1)); + } + + public static int setXAttr(String path, String name, String value) { + Memory valueBuffer = encodeString(value); + return XAttr.INSTANCE.setxattr(path, name, valueBuffer, valueBuffer.size(), 0, 0); + } + + public static int removeXAttr(String path, String name) { + return XAttr.INSTANCE.removexattr(path, name, 0); + } + + protected static Memory encodeString(String s) { + // create NULL-terminated UTF-8 String + byte[] bb = s.getBytes(Charset.forName("UTF-8")); + Memory valueBuffer = new Memory(bb.length + 1); + valueBuffer.write(0, bb, 0, bb.length); + valueBuffer.setByte(valueBuffer.size() - 1, (byte) 0); + return valueBuffer; + } + + protected static String decodeString(ByteBuffer bb) { + return Charset.forName("UTF-8").decode(bb).toString(); + } + + protected static List decodeStringSequence(ByteBuffer bb) { + List names = new ArrayList(); + + bb.mark(); // first key starts from here + while (bb.hasRemaining()) { + if (bb.get() == 0) { + ByteBuffer nameBuffer = (ByteBuffer) bb.duplicate().limit(bb.position() - 1).reset(); + if (nameBuffer.hasRemaining()) { + names.add(decodeString(nameBuffer)); + } + bb.mark(); // next key starts from here + } + } + + return names; + } + +}