    1   /* ObjectPlusFilesOutputStream
    2   *
    3   * $Id: ObjectPlusFilesOutputStream.java 4646 2006-09-22 17:23:04Z paul_jack $
    4   *
    5   * Created on Apr 28, 2004
    6   *
    7   * Copyright (C) 2004 Internet Archive.
    8   *
    9   * This file is part of the Heritrix web crawler (crawler.archive.org).
    10  *
    11  * Heritrix is free software; you can redistribute it and/or modify
    12  * it under the terms of the GNU Lesser Public License as published by
    13  * the Free Software Foundation; either version 2.1 of the License, or
    14  * any later version.
    15  *
    16  * Heritrix is distributed in the hope that it will be useful,
    17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    19  * GNU Lesser Public License for more details.
    20  *
    21  * You should have received a copy of the GNU Lesser Public License
    22  * along with Heritrix; if not, write to the Free Software
    23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    24  */
    25  package org.archive.io;
    26
    27  import java.io.File;
    28  import java.io.IOException;
    29  import java.io.ObjectOutputStream;
    30  import java.io.OutputStream;
    31  import java.util.LinkedList;
    32
    33  import org.archive.util.FileUtils;
    34
    35
    36  /***
    37   * Enhanced ObjectOutputStream which maintains (a stack of) auxiliary
    38   * directories and offers convenience methods for serialized objects
    39   * to save their related disk files alongside their serialized version.
    40   *
    41   * @author gojomo
    42   */
    43  public class ObjectPlusFilesOutputStream extends ObjectOutputStream {
    44      LinkedList<File> auxiliaryDirectoryStack = new LinkedList<File>();
    45
    46      /***
    47       * Constructor
    48       *
    49       * @param out
    50       * @param topDirectory
    51       * @throws java.io.IOException
    52       */
    53      public ObjectPlusFilesOutputStream(OutputStream out, File topDirectory) throws IOException {
    54          super(out);
    55          auxiliaryDirectoryStack.addFirst(topDirectory);
    56      }
    57
    58      /***
    59       * Add another subdirectory for any file-capture needs during the
    60       * current serialization.
    61       *
    62       * @param dir
    63       */
    64      public void pushAuxiliaryDirectory(String dir) {
    65          auxiliaryDirectoryStack.addFirst(new File(getAuxiliaryDirectory(),dir));
    66      }
    67
    68      /***
    69       * Remove the top subdirectory.
    70       *
    71       */
    72      public void popAuxiliaryDirectory() {
    73          auxiliaryDirectoryStack.removeFirst();
    74      }
    75
    76      /***
    77       * Return the current auxiliary directory for storing
    78       * files associated with serialized objects.
    79       *
    80       * @return Auxillary directory.
    81       */
    82      public File getAuxiliaryDirectory() {
    83          return (File)auxiliaryDirectoryStack.getFirst();
    84      }
    85
    86      /***
    87       * Store a snapshot of an object's supporting file to the
    88       * current auxiliary directory. Should only be used for
    89       * files which are strictly appended-to, because it tries
    90       * to use a "hard link" where possible (meaning that
    91       * future edits to the original file's contents will
    92       * also affect the snapshot).
    93       *
    94       * Remembers current file extent to allow a future restore
    95       * to ignore subsequent appended data.
    96       *
    97       * @param file
    98       * @throws IOException
    99       */
    100     public void snapshotAppendOnlyFile(File file) throws IOException {
    101         // write filename
    102         String name = file.getName();
    103         writeUTF(name);
    104         // write current file length
    105         writeLong(file.length());
    106         File auxDir = getAuxiliaryDirectory();
    107         if(!auxDir.exists()) {
    108         	auxDir.mkdirs();
    109         }
    110         File destination = new File(auxDir,name);
    111         hardlinkOrCopy(file, destination);
    112     }
    113
    114 	/***
    115      * Create a backup of this given file, first by trying a "hard
    116      * link", then by using a copy if hard linking is unavailable
    117      * (either because it is unsupported or the origin and checkpoint
    118      * directories are on different volumes).
    119      *
    120 	 * @param file
    121 	 * @param destination
    122      * @throws IOException
    123 	 */
    124 	private void hardlinkOrCopy(File file, File destination) throws IOException {
    125 		// For Linux/UNIX, try a hard link first.
    126         Process link = Runtime.getRuntime().exec("ln "+file.getAbsolutePath()+" "+destination.getAbsolutePath());
    127         // TODO NTFS also supports hard links; add appropriate try
    128         try {
    129 			link.waitFor();
    130 		} catch (InterruptedException e) {
    131 			// TODO Auto-generated catch block
    132 			e.printStackTrace();
    133 		}
    134         if(link.exitValue()!=0) {
    135         	// hard link failed
    136             FileUtils.copyFile(file,destination);
    137         }
    138 	}
    139
    140 }