/* ========================================================================= FmqFile - work with files ------------------------------------------------------------------------- Copyright (c) 1991-2012 iMatix Corporation -- http://www.imatix.com Copyright other contributors as noted in the AUTHORS file. This file is part of FILEMQ, see http://filemq.org. This 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 3 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTA- BILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see http://www.gnu.org/licenses/. ========================================================================= */ package org.filemq; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FmqFile { private File path; private String name; // File name with path private String link; // Optional linked file // Properties from file system private long time; // Modification time private long size; // Size of the file //mode_t mode; // POSIX permission bits // Other properties private boolean exists; // true if file exists private boolean stable; // true if file is stable private boolean eof; // true if at end of file private FileChannel handle; // Read/write handle public FmqFile (final String parent, final String name) { this (new File (parent, name)); } public FmqFile (final File parent, final String name) { this (new File (parent, name)); } public FmqFile (File path) { this.path = path; name = path.getAbsolutePath (); if (path.exists () && path.getName ().endsWith (".ln")) { BufferedReader in = null; try { in = new BufferedReader (new FileReader (path)); String buffer = in.readLine (); link = buffer.trim (); if (link == null) { // There could be a race condition or corrupted, try once more in.close (); in = new BufferedReader (new FileReader (path)); buffer = in.readLine (); link = buffer.trim (); } if (link == null) { // Guess it is corrupted in.close (); in = null; path.delete (); } else { this.path = new File (link); name = name.substring (0, name.length () -3); } } catch (IOException e) { } finally { if (in != null) try { in.close (); } catch (IOException e) { } } } restat (); } // -------------------------------------------------------------------------- // Destroy a file item public void destroy () { if (handle != null) try { handle.close (); } catch (IOException e) { } } // -------------------------------------------------------------------------- // Duplicate a file item public FmqFile dup () { FmqFile copy = new FmqFile (path); return copy; } // -------------------------------------------------------------------------- // Return file name, remove prefix if provided public String name (String prefix) { if (prefix == null) return name; String parent = new File (prefix).getAbsolutePath (); if (name.startsWith (parent)) { String result = name.substring (parent.length ()); if (result.startsWith ("/")) result = result.substring (1); return result; } return name; } // -------------------------------------------------------------------------- // Refreshes file properties from file system public void restat () { if (path.exists ()) { // Not sure if stat mtime is fully portable exists = true; size = path.length (); time = path.lastModified (); // File is 'stable' if more than 1 second old long age = System.currentTimeMillis () - time; stable = age > 1000; } else exists = false; } // -------------------------------------------------------------------------- // Check if file exists/ed; does not restat file public boolean exists () { return exists; } // -------------------------------------------------------------------------- // Check if file is/was stable; does not restat file public boolean stable () { return stable; } // -------------------------------------------------------------------------- // Remove the file public void remove () { if (link != null) new File (name + ".ln").delete (); else path.delete (); } // -------------------------------------------------------------------------- // Open file for reading // Returns 0 if OK, -1 if not found or not accessible // If file is symlink, opens real physical file, not link public boolean input () { if (handle != null) close (); try { handle = new RandomAccessFile (path, "r").getChannel (); size = path.length (); } catch (FileNotFoundException e) { size = 0; return false; } return true; } // -------------------------------------------------------------------------- // Open file for writing, creating directory if needed // File is created if necessary; chunks can be written to file at any // location. Returns 0 if OK, -1 if error. // If file was symbolic link, that's over-written public boolean output () { // Wipe symbolic link if that's what the file was if (link != null) { link = null; } path.getParentFile ().mkdirs (); if (handle != null) close (); // Create file if it doesn't exist try { handle = new RandomAccessFile (path, "rw").getChannel (); } catch (FileNotFoundException e) { return false; } return true; } // -------------------------------------------------------------------------- // Read chunk from file at specified position // If this was the last chunk, sets self->eof // Null chunk means there was another error public FmqChunk read (int bytes, long offset) { assert (handle != null); // Calculate real number of bytes to read if (offset > size) bytes = 0; else if (bytes > size - offset) bytes = (int) (size - offset); try { handle.position (offset); } catch (IOException e) { return null; } eof = false; FmqChunk chunk = FmqChunk.read (handle, bytes); if (chunk != null) eof = chunk.size () < bytes; return chunk; } // -------------------------------------------------------------------------- // Write chunk to file at specified position // Return 0 if OK, else -1 public boolean write (FmqChunk chunk, long offset) { assert (handle != null); try { handle.position (offset); } catch (IOException e) { return false; } return chunk.write (handle); } // -------------------------------------------------------------------------- // Write string to file at specified position // Return 0 if OK, else -1 public boolean write (String data, long offset) { assert (handle != null); try { handle.position (offset); handle.write (ByteBuffer.wrap (data.getBytes ())); return true; } catch (IOException e) { return false; } } // -------------------------------------------------------------------------- // Close file, if open public void close () { if (handle != null) { try { handle.close (); } catch (IOException e) { } restat (); } handle = null; } // -------------------------------------------------------------------------- // Return file handle, if opened public FileChannel handle () { return handle; } // -------------------------------------------------------------------------- // Return file SHA-1 hash as string; public String hash () { boolean rc = input (); if (!rc) return null; // Problem reading directory // Now calculate hash for file data, chunk by chunk FmqHash hash = new FmqHash (); int blocksz = 65535; FmqChunk chunk = FmqChunk.read (handle, blocksz); while (chunk.size () > 0) { hash.update (chunk.data (), chunk.size ()); chunk.destroy (); chunk = FmqChunk.read (handle, blocksz); } chunk.destroy (); close (); // Convert to printable string byte [] hex_char = "0123456789ABCDEF".getBytes (); byte [] hashstr = new byte [hash.size () * 2 + 1]; byte [] data = hash.data (); int byte_nbr; for (byte_nbr = 0; byte_nbr < hash.size (); byte_nbr++) { hashstr [byte_nbr * 2 + 0] = hex_char [(0xff & data [byte_nbr]) >> 4]; hashstr [byte_nbr * 2 + 1] = hex_char [data [byte_nbr] & 15]; } hash.destroy (); return new String (hashstr); } public long time () { return time; } public long size () { return size; } }