mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-26 00:00:07 -04:00
moving files around
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links.
|
||||
* <p/>
|
||||
* This class is based on example code from
|
||||
* <a href="http://www.oreilly.com/catalog/swinghks/index.html">Swing Hacks</a>,
|
||||
* By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32Lnk.java#2 $
|
||||
*/
|
||||
final class Win32Lnk extends File {
|
||||
private final static byte[] LNK_MAGIC = {
|
||||
'L', 0x00, 0x00, 0x00, // Magic
|
||||
};
|
||||
private final static byte[] LNK_GUID = {
|
||||
0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID
|
||||
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
|
||||
};
|
||||
|
||||
private final File mTarget;
|
||||
private static final int FLAG_ITEM_ID_LIST = 0x01;
|
||||
private static final int FLAG_FILE_LOC_INFO = 0x02;
|
||||
private static final int FLAG_DESC_STRING = 0x04;
|
||||
private static final int FLAG_REL_PATH_STRING = 0x08;
|
||||
private static final int FLAG_WORKING_DIRECTORY = 0x10;
|
||||
private static final int FLAG_COMMAND_LINE_ARGS = 0x20;
|
||||
private static final int FLAG_ICON_FILENAME = 0x40;
|
||||
private static final int FLAG_ADDITIONAL_INFO = 0x80;
|
||||
|
||||
private Win32Lnk(final String pPath) throws IOException {
|
||||
super(pPath);
|
||||
File target = parse(this);
|
||||
if (target == this) {
|
||||
// NOTE: This is a workaround
|
||||
// mTarget = this causes infinite loops in some methods
|
||||
target = new File(pPath);
|
||||
}
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
Win32Lnk(final File pPath) throws IOException {
|
||||
this(pPath.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a {@code .lnk} file to find the real file.
|
||||
*
|
||||
* @param pPath the path to the {@code .lnk} file
|
||||
* @return a new file object that
|
||||
* @throws java.io.IOException if the {@code .lnk} cannot be parsed
|
||||
*/
|
||||
static File parse(final File pPath) throws IOException {
|
||||
if (!pPath.getName().endsWith(".lnk")) {
|
||||
return pPath;
|
||||
}
|
||||
|
||||
File result = pPath;
|
||||
|
||||
LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath)));
|
||||
try {
|
||||
byte[] magic = new byte[4];
|
||||
in.readFully(magic);
|
||||
|
||||
byte[] guid = new byte[16];
|
||||
in.readFully(guid);
|
||||
|
||||
if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) {
|
||||
//System.out.println("Not a symlink");
|
||||
// Not a symlink
|
||||
return pPath;
|
||||
}
|
||||
|
||||
// Get the flags
|
||||
int flags = in.readInt();
|
||||
//System.out.println("flags: " + Integer.toBinaryString(flags & 0xff));
|
||||
|
||||
// Get to the file settings
|
||||
/*int attributes = */in.readInt();
|
||||
|
||||
// File attributes
|
||||
// 0 Target is read only.
|
||||
// 1 Target is hidden.
|
||||
// 2 Target is a system file.
|
||||
// 3 Target is a volume label. (Not possible)
|
||||
// 4 Target is a directory.
|
||||
// 5 Target has been modified since last backup. (archive)
|
||||
// 6 Target is encrypted (NTFS EFS)
|
||||
// 7 Target is Normal??
|
||||
// 8 Target is temporary.
|
||||
// 9 Target is a sparse file.
|
||||
// 10 Target has reparse point data.
|
||||
// 11 Target is compressed.
|
||||
// 12 Target is offline.
|
||||
//System.out.println("attributes: " + Integer.toBinaryString(attributes));
|
||||
// NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/
|
||||
|
||||
in.skipBytes(48); // TODO: Make sense of this data...
|
||||
|
||||
// Skipped data:
|
||||
// long time 1 (creation)
|
||||
// long time 2 (modification)
|
||||
// long time 3 (last access)
|
||||
// int file length
|
||||
// int icon number
|
||||
// int ShowVnd value
|
||||
// int hotkey
|
||||
// int, int - unknown: 0,0
|
||||
|
||||
// If the shell settings are present, skip them
|
||||
if ((flags & FLAG_ITEM_ID_LIST) != 0) {
|
||||
// Shell Item Id List present
|
||||
//System.out.println("Shell Item Id List present");
|
||||
int shellLen = in.readShort(); // Short
|
||||
//System.out.println("shellLen: " + shellLen);
|
||||
|
||||
// TODO: Probably need to parse this data, to determine
|
||||
// Cygwin folders...
|
||||
|
||||
/*
|
||||
int read = 2;
|
||||
int itemLen = in.readShort();
|
||||
while (itemLen > 0) {
|
||||
System.out.println("--> ITEM: " + itemLen);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2)));
|
||||
//byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included
|
||||
//in.readFully(itemBytes);
|
||||
|
||||
String item = reader.readLine();
|
||||
System.out.println("item: \"" + item + "\"");
|
||||
|
||||
itemLen = in.readShort();
|
||||
read += itemLen;
|
||||
}
|
||||
|
||||
System.out.println("read: " + read);
|
||||
*/
|
||||
|
||||
in.skipBytes(shellLen);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_FILE_LOC_INFO) != 0) {
|
||||
// File Location Info Table present
|
||||
//System.out.println("File Location Info Table present");
|
||||
|
||||
// 0h 1 dword This is the total length of this structure and all following data
|
||||
// 4h 1 dword This is a pointer to first offset after this structure. 1Ch
|
||||
// 8h 1 dword Flags
|
||||
// Ch 1 dword Offset of local volume info
|
||||
// 10h 1 dword Offset of base pathname on local system
|
||||
// 14h 1 dword Offset of network volume info
|
||||
// 18h 1 dword Offset of remaining pathname
|
||||
|
||||
// Flags:
|
||||
// Bit Meaning
|
||||
// 0 Available on a local volume
|
||||
// 1 Available on a network share
|
||||
// TODO: Make sure the path is on a local disk, etc..
|
||||
|
||||
int tableLen = in.readInt(); // Int
|
||||
//System.out.println("tableLen: " + tableLen);
|
||||
|
||||
in.readInt(); // Skip
|
||||
|
||||
int locFlags = in.readInt();
|
||||
//System.out.println("locFlags: " + Integer.toBinaryString(locFlags));
|
||||
if ((locFlags & 0x01) != 0) {
|
||||
//System.out.println("Available local");
|
||||
}
|
||||
if ((locFlags & 0x02) != 0) {
|
||||
//System.err.println("Available on network path");
|
||||
}
|
||||
|
||||
// Get the local volume and local system values
|
||||
in.skipBytes(4); // TODO: see above for structure
|
||||
|
||||
int localSysOff = in.readInt();
|
||||
//System.out.println("localSysOff: " + localSysOff);
|
||||
in.skipBytes(localSysOff - 20); // Relative to start of chunk
|
||||
|
||||
byte[] pathBytes = new byte[tableLen - localSysOff - 1];
|
||||
in.readFully(pathBytes, 0, pathBytes.length);
|
||||
String path = new String(pathBytes, 0, pathBytes.length - 1);
|
||||
/*
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
byte read;
|
||||
// Read bytes until the null (0) character
|
||||
while (true) {
|
||||
read = in.readByte();
|
||||
if (read == 0) {
|
||||
break;
|
||||
}
|
||||
bytes.write(read & 0xff);
|
||||
}
|
||||
|
||||
String path = new String(bytes.toByteArray(), 0, bytes.size());
|
||||
//*/
|
||||
|
||||
// Recurse to end of link chain
|
||||
// TODO: This may cause endless loop if cyclic chain...
|
||||
//System.out.println("path: \"" + path + "\"");
|
||||
try {
|
||||
result = parse(new File(path));
|
||||
}
|
||||
catch (StackOverflowError e) {
|
||||
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FLAG_DESC_STRING) != 0) {
|
||||
// Description String present, skip it.
|
||||
//System.out.println("Description String present");
|
||||
|
||||
// The string length is the first word which must also be skipped.
|
||||
int descLen = in.readShort();
|
||||
//System.out.println("descLen: " + descLen);
|
||||
|
||||
byte[] descBytes = new byte[descLen];
|
||||
in.readFully(descBytes, 0, descLen);
|
||||
|
||||
//String desc = new String(descBytes, 0, descLen);
|
||||
//System.out.println("desc: " + desc);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_REL_PATH_STRING) != 0) {
|
||||
// Relative Path String present
|
||||
//System.out.println("Relative Path String present");
|
||||
|
||||
// The string length is the first word which must also be skipped.
|
||||
int pathLen = in.readShort();
|
||||
//System.out.println("pathLen: " + pathLen);
|
||||
|
||||
byte[] pathBytes = new byte[pathLen];
|
||||
in.readFully(pathBytes, 0, pathLen);
|
||||
|
||||
String path = new String(pathBytes, 0, pathLen);
|
||||
|
||||
// TODO: This may cause endless loop if cyclic chain...
|
||||
//System.out.println("path: \"" + path + "\"");
|
||||
if (result == pPath) {
|
||||
try {
|
||||
result = parse(new File(pPath.getParentFile(), path));
|
||||
}
|
||||
catch (StackOverflowError e) {
|
||||
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FLAG_WORKING_DIRECTORY) != 0) {
|
||||
//System.out.println("Working Directory present");
|
||||
}
|
||||
if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) {
|
||||
//System.out.println("Command Line Arguments present");
|
||||
// NOTE: This means this .lnk is not a folder, don't follow
|
||||
result = pPath;
|
||||
}
|
||||
if ((flags & FLAG_ICON_FILENAME) != 0) {
|
||||
//System.out.println("Icon Filename present");
|
||||
}
|
||||
if ((flags & FLAG_ADDITIONAL_INFO) != 0) {
|
||||
//System.out.println("Additional Info present");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
private static String getNullDelimitedString(byte[] bytes, int off) {
|
||||
int len = 0;
|
||||
// Count bytes until the null (0) character
|
||||
while (true) {
|
||||
if (bytes[off + len] == 0) {
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
System.err.println("--> " + len);
|
||||
|
||||
return new String(bytes, off, len);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts two bytes into a short.
|
||||
* <p/>
|
||||
* NOTE: this is little endian because it's for an
|
||||
* Intel only OS
|
||||
*
|
||||
* @ param bytes
|
||||
* @ param off
|
||||
* @return the bytes as a short.
|
||||
*/
|
||||
/*
|
||||
private static int bytes2short(byte[] bytes, int off) {
|
||||
return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
|
||||
}
|
||||
*/
|
||||
|
||||
public File getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
// java.io.File overrides below
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return mTarget.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return mTarget.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
return mTarget.canWrite();
|
||||
}
|
||||
|
||||
// NOTE: equals is implemented using compareto == 0
|
||||
/*
|
||||
public int compareTo(File pathname) {
|
||||
// TODO: Verify this
|
||||
// Probably not a good idea, as it IS NOT THE SAME file
|
||||
// It's probably better to not override
|
||||
return mTarget.compareTo(pathname);
|
||||
}
|
||||
*/
|
||||
|
||||
// Should probably never allow creating a new .lnk
|
||||
// public boolean createNewFile() throws IOException
|
||||
|
||||
// Deletes only the .lnk
|
||||
// public boolean delete() {
|
||||
//public void deleteOnExit() {
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return mTarget.exists();
|
||||
}
|
||||
|
||||
// A .lnk may be absolute
|
||||
//public File getAbsoluteFile() {
|
||||
//public String getAbsolutePath() {
|
||||
|
||||
// Theses should be resolved according to the API (for Unix).
|
||||
@Override
|
||||
public File getCanonicalFile() throws IOException {
|
||||
return mTarget.getCanonicalFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() throws IOException {
|
||||
return mTarget.getCanonicalPath();
|
||||
}
|
||||
|
||||
//public String getName() {
|
||||
|
||||
// I guess the parent should be the parent of the .lnk, not the target
|
||||
//public String getParent() {
|
||||
//public File getParentFile() {
|
||||
|
||||
// public boolean isAbsolute() {
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return mTarget.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden() {
|
||||
return mTarget.isHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return mTarget.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mTarget.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list() {
|
||||
return mTarget.list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(final FilenameFilter filter) {
|
||||
return mTarget.list(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles() {
|
||||
return Win32File.wrap(mTarget.listFiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FileFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FilenameFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
}
|
||||
|
||||
// Makes no sense, does it?
|
||||
//public boolean mkdir() {
|
||||
//public boolean mkdirs() {
|
||||
|
||||
// Only rename the lnk
|
||||
//public boolean renameTo(File dest) {
|
||||
|
||||
@Override
|
||||
public boolean setLastModified(long time) {
|
||||
return mTarget.setLastModified(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly() {
|
||||
return mTarget.setReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mTarget.equals(this)) {
|
||||
return super.toString();
|
||||
}
|
||||
return super.toString() + " -> " + mTarget.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user