1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
102 String name = file.getName();
103 writeUTF(name);
104
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
126 Process link = Runtime.getRuntime().exec("ln "+file.getAbsolutePath()+" "+destination.getAbsolutePath());
127
128 try {
129 link.waitFor();
130 } catch (InterruptedException e) {
131
132 e.printStackTrace();
133 }
134 if(link.exitValue()!=0) {
135
136 FileUtils.copyFile(file,destination);
137 }
138 }
139
140 }