/hudson-core/src/main/java/hudson/model/LargeText.java

http://github.com/hudson/hudson · Java · 372 lines · 232 code · 55 blank · 85 comment · 15 complexity · 2bebbda87a1f25d155daae34b09a0c4d MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson.model;
  25. import hudson.util.ByteBuffer;
  26. import hudson.util.CharSpool;
  27. import hudson.util.LineEndNormalizingWriter;
  28. import org.kohsuke.stapler.StaplerRequest;
  29. import org.kohsuke.stapler.StaplerResponse;
  30. import org.kohsuke.stapler.framework.io.WriterOutputStream;
  31. import org.apache.commons.io.output.CountingOutputStream;
  32. import javax.servlet.http.HttpServletResponse;
  33. import java.io.File;
  34. import java.io.IOException;
  35. import java.io.InputStream;
  36. import java.io.OutputStream;
  37. import java.io.RandomAccessFile;
  38. import java.io.Writer;
  39. import java.io.Reader;
  40. import java.io.InputStreamReader;
  41. /**
  42. * Represents a large text data.
  43. *
  44. * <p>
  45. * This class defines methods for handling progressive text update.
  46. *
  47. * @author Kohsuke Kawaguchi
  48. * @deprecated moved to stapler, as of Hudson 1.220
  49. */
  50. public class LargeText {
  51. /**
  52. * Represents the data source of this text.
  53. */
  54. private interface Source {
  55. Session open() throws IOException;
  56. long length();
  57. boolean exists();
  58. }
  59. private final Source source;
  60. private volatile boolean completed;
  61. public LargeText(final File file, boolean completed) {
  62. this.source = new Source() {
  63. public Session open() throws IOException {
  64. return new FileSession(file);
  65. }
  66. public long length() {
  67. return file.length();
  68. }
  69. public boolean exists() {
  70. return file.exists();
  71. }
  72. };
  73. this.completed = completed;
  74. }
  75. public LargeText(final ByteBuffer memory, boolean completed) {
  76. this.source = new Source() {
  77. public Session open() throws IOException {
  78. return new BufferSession(memory);
  79. }
  80. public long length() {
  81. return memory.length();
  82. }
  83. public boolean exists() {
  84. return true;
  85. }
  86. };
  87. this.completed = completed;
  88. }
  89. public void markAsComplete() {
  90. completed = true;
  91. }
  92. public boolean isComplete() {
  93. return completed;
  94. }
  95. /**
  96. * Returns {@link Reader} for reading the raw bytes.
  97. */
  98. public Reader readAll() throws IOException {
  99. return new InputStreamReader(new InputStream() {
  100. final Session session = source.open();
  101. public int read() throws IOException {
  102. byte[] buf = new byte[1];
  103. int n = session.read(buf);
  104. if(n==1) return buf[0];
  105. else return -1; // EOF
  106. }
  107. public int read(byte[] buf, int off, int len) throws IOException {
  108. return session.read(buf,off,len);
  109. }
  110. public void close() throws IOException {
  111. session.close();
  112. }
  113. });
  114. }
  115. /**
  116. * Writes the tail portion of the file to the {@link Writer}.
  117. *
  118. * <p>
  119. * The text file is assumed to be in the system default encoding.
  120. *
  121. * @param start
  122. * The byte offset in the input file where the write operation starts.
  123. *
  124. * @return
  125. * if the file is still being written, this method writes the file
  126. * until the last newline character and returns the offset to start
  127. * the next write operation.
  128. */
  129. public long writeLogTo(long start, Writer w) throws IOException {
  130. CountingOutputStream os = new CountingOutputStream(new WriterOutputStream(w));
  131. Session f = source.open();
  132. f.skip(start);
  133. if(completed) {
  134. // write everything till EOF
  135. byte[] buf = new byte[1024];
  136. int sz;
  137. while((sz=f.read(buf))>=0)
  138. os.write(buf,0,sz);
  139. } else {
  140. ByteBuf buf = new ByteBuf(null,f);
  141. HeadMark head = new HeadMark(buf);
  142. TailMark tail = new TailMark(buf);
  143. while(tail.moveToNextLine(f)) {
  144. head.moveTo(tail,os);
  145. }
  146. head.finish(os);
  147. }
  148. f.close();
  149. os.flush();
  150. return os.getCount()+start;
  151. }
  152. /**
  153. * Implements the progressive text handling.
  154. * This method is used as a "web method" with progressiveText.jelly.
  155. */
  156. public void doProgressText(StaplerRequest req, StaplerResponse rsp) throws IOException {
  157. rsp.setContentType("text/plain");
  158. rsp.setStatus(HttpServletResponse.SC_OK);
  159. if(!source.exists()) {
  160. // file doesn't exist yet
  161. rsp.addHeader("X-Text-Size","0");
  162. rsp.addHeader("X-More-Data","true");
  163. return;
  164. }
  165. long start = 0;
  166. String s = req.getParameter("start");
  167. if(s!=null)
  168. start = Long.parseLong(s);
  169. if(source.length() < start )
  170. start = 0; // text rolled over
  171. CharSpool spool = new CharSpool();
  172. long r = writeLogTo(start,spool);
  173. rsp.addHeader("X-Text-Size",String.valueOf(r));
  174. if(!completed)
  175. rsp.addHeader("X-More-Data","true");
  176. // when sending big text, try compression. don't bother if it's small
  177. Writer w;
  178. if(r-start>4096)
  179. w = rsp.getCompressedWriter(req);
  180. else
  181. w = rsp.getWriter();
  182. spool.writeTo(new LineEndNormalizingWriter(w));
  183. w.close();
  184. }
  185. /**
  186. * Points to a byte in the buffer.
  187. */
  188. private static class Mark {
  189. protected ByteBuf buf;
  190. protected int pos;
  191. public Mark(ByteBuf buf) {
  192. this.buf = buf;
  193. }
  194. }
  195. /**
  196. * Points to the start of the region that's not committed
  197. * to the output yet.
  198. */
  199. private static final class HeadMark extends Mark {
  200. public HeadMark(ByteBuf buf) {
  201. super(buf);
  202. }
  203. /**
  204. * Moves this mark to 'that' mark, and writes the data
  205. * to {@link OutputStream} if necessary.
  206. */
  207. void moveTo(Mark that, OutputStream os) throws IOException {
  208. while(this.buf!=that.buf) {
  209. os.write(buf.buf,0,buf.size);
  210. buf = buf.next;
  211. pos = 0;
  212. }
  213. this.pos = that.pos;
  214. }
  215. void finish(OutputStream os) throws IOException {
  216. os.write(buf.buf,0,pos);
  217. }
  218. }
  219. /**
  220. * Points to the end of the region.
  221. */
  222. private static final class TailMark extends Mark {
  223. public TailMark(ByteBuf buf) {
  224. super(buf);
  225. }
  226. boolean moveToNextLine(Session f) throws IOException {
  227. while(true) {
  228. while(pos==buf.size) {
  229. if(!buf.isFull()) {
  230. // read until EOF
  231. return false;
  232. } else {
  233. // read into the next buffer
  234. buf = new ByteBuf(buf,f);
  235. pos = 0;
  236. }
  237. }
  238. byte b = buf.buf[pos++];
  239. if(b=='\r' || b=='\n')
  240. return true;
  241. }
  242. }
  243. }
  244. private static final class ByteBuf {
  245. private final byte[] buf = new byte[1024];
  246. private int size = 0;
  247. private ByteBuf next;
  248. public ByteBuf(ByteBuf previous, Session f) throws IOException {
  249. if(previous!=null) {
  250. assert previous.next==null;
  251. previous.next = this;
  252. }
  253. while(!this.isFull()) {
  254. int chunk = f.read(buf, size, buf.length - size);
  255. if(chunk==-1)
  256. return;
  257. size+= chunk;
  258. }
  259. }
  260. public boolean isFull() {
  261. return buf.length==size;
  262. }
  263. }
  264. /**
  265. * Represents the read session of the {@link Source}.
  266. * Methods generally follow the contracts of {@link InputStream}.
  267. */
  268. private interface Session {
  269. void close() throws IOException;
  270. void skip(long start) throws IOException;
  271. int read(byte[] buf) throws IOException;
  272. int read(byte[] buf, int offset, int length) throws IOException;
  273. }
  274. /**
  275. * {@link Session} implementation over {@link RandomAccessFile}.
  276. */
  277. private static final class FileSession implements Session {
  278. private final RandomAccessFile file;
  279. public FileSession(File file) throws IOException {
  280. this.file = new RandomAccessFile(file,"r");
  281. }
  282. public void close() throws IOException {
  283. file.close();
  284. }
  285. public void skip(long start) throws IOException {
  286. file.seek(file.getFilePointer()+start);
  287. }
  288. public int read(byte[] buf) throws IOException {
  289. return file.read(buf);
  290. }
  291. public int read(byte[] buf, int offset, int length) throws IOException {
  292. return file.read(buf,offset,length);
  293. }
  294. }
  295. /**
  296. * {@link Session} implementation over {@link ByteBuffer}.
  297. */
  298. private static final class BufferSession implements Session {
  299. private final InputStream in;
  300. public BufferSession(ByteBuffer buf) {
  301. this.in = buf.newInputStream();
  302. }
  303. public void close() throws IOException {
  304. in.close();
  305. }
  306. public void skip(long n) throws IOException {
  307. while(n>0)
  308. n -= in.skip(n);
  309. }
  310. public int read(byte[] buf) throws IOException {
  311. return in.read(buf);
  312. }
  313. public int read(byte[] buf, int offset, int length) throws IOException {
  314. return in.read(buf,offset,length);
  315. }
  316. }
  317. }