1 package org.daisy.pipeline.file.calabash.impl;
2
3 import java.io.InputStream;
4 import java.io.IOException;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.nio.file.Path;
8 import java.nio.file.Files;
9 import java.util.Map;
10
11 import com.xmlcalabash.core.XProcConstants;
12 import com.xmlcalabash.core.XProcRuntime;
13 import com.xmlcalabash.io.WritablePipe;
14 import com.xmlcalabash.library.DefaultStep;
15 import com.xmlcalabash.model.RuntimeValue;
16 import com.xmlcalabash.runtime.XAtomicStep;
17 import com.xmlcalabash.util.Base64;
18 import com.xmlcalabash.util.TreeWriter;
19
20 import net.sf.saxon.s9api.QName;
21 import net.sf.saxon.s9api.SaxonApiException;
22
23 import org.daisy.common.xproc.calabash.XProcStep;
24 import org.daisy.common.xproc.calabash.XProcStepProvider;
25 import org.daisy.common.xproc.XProcMonitor;
26 import org.daisy.pipeline.file.FileUtils;
27
28 import org.osgi.service.component.annotations.Component;
29
30 @Component(
31 name = "pxi:file-peek",
32 service = { XProcStepProvider.class },
33 property = { "type:String={http://www.daisy.org/ns/pipeline/xproc/internal}file-peek" }
34 )
35 public class PeekProvider implements XProcStepProvider {
36
37 @Override
38 public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
39 return new Peek(runtime, step);
40 }
41
42
43
44
45 public static class Peek extends DefaultStep implements XProcStep {
46 private static final QName _href = new QName("href");
47 private static final QName _offset = new QName("offset");
48 private static final QName _length = new QName("length");
49
50 private WritablePipe result = null;
51
52 public Peek(XProcRuntime runtime, XAtomicStep step) {
53 super(runtime,step);
54 }
55
56 public void setOutput(String port, WritablePipe pipe) {
57 result = pipe;
58 }
59
60 public void reset() {
61 result.resetWriter();
62 }
63
64 public void run() throws SaxonApiException {
65 super.run();
66
67 int offset = getOption(_offset, 0);
68 int length = getOption(_length, 0);
69
70 RuntimeValue href = getOption(_href);
71 URI sourceUri = href.getBaseURI().resolve(href.getString());
72 Path file; {
73 try {
74 file = FileUtils.asPath(sourceUri);
75 } catch (IllegalArgumentException|URISyntaxException e) {
76 throw XProcStep.raiseError(new IllegalArgumentException("Illegal value for href option: " + href.getString(), e), step);
77 }
78 }
79
80 if (Files.isDirectory(file)) {
81 throw XProcStep.raiseError(new IllegalArgumentException("Cannot peek into file: file is a directory: " + file), step);
82 }
83
84 byte[] resultBytes = null;
85 try {
86 resultBytes = Peek.read(file, offset, length);
87 } catch (IOException | IndexOutOfBoundsException e) {
88 String fileSize; {
89 try {
90 fileSize = "" + Files.size(file);
91 } catch (IOException ee) {
92 fileSize = "?";
93 }
94 }
95 logger.error("px:file-peek failed to read from "+file+" (offset: "+offset+", length: "+length+", filesize: "+fileSize+")", e);
96 e.printStackTrace();
97 }
98
99 TreeWriter tree = new TreeWriter(runtime);
100 tree.startDocument(step.getNode().getBaseURI());
101 tree.addStartElement(XProcConstants.c_result);
102 tree.addAttribute(new QName("content-type"), "binary/octet-stream");
103 tree.addAttribute(new QName("encoding"), "base64");
104 tree.startContent();
105
106 if (resultBytes == null) {
107 String fileSize; {
108 try {
109 fileSize = "" + Files.size(file);
110 } catch (IOException ee) {
111 fileSize = "?";
112 }
113 }
114 tree.addAttribute(new QName("error"), "px:file-peek failed to read from "+file+" (offset: "+offset+", length: "+length+", filesize: "+fileSize+")");
115 } else {
116 tree.addText(Peek.encodeBase64(resultBytes));
117 }
118
119 tree.addEndElement();
120 tree.endDocument();
121
122 result.write(tree.getResult());
123 }
124
125 public static byte[] read(Path file, int offset, int length) throws IOException, IndexOutOfBoundsException {
126 long fileSize = Files.size(file);
127 if (offset > fileSize) {
128 offset = 0;
129 length = 0;
130 }
131 if (length + offset > fileSize) {
132 length = (int) (fileSize - offset);
133 }
134
135 InputStream fis = null;
136 byte[] resultBytes = new byte[length];
137 if (length > 0) {
138 try {
139 if (Files.exists(file) && Files.isReadable(file)) {
140 fis = Files.newInputStream(file);
141 fis.skip(offset);
142 fis.read(resultBytes, 0, length);
143 }
144
145 } finally {
146 try {
147 fis.close();
148 } catch (IOException e) {
149 }
150 }
151 }
152
153 return resultBytes;
154 }
155
156 public static String encodeBase64(byte[] bytes) {
157 return Base64.encodeBytes(bytes);
158 }
159 }
160
161 }