/* * This file is part of the Heritrix web crawler (crawler.archive.org). * * Licensed to the Internet Archive (IA) by one or more individual * contributors. * * The IA licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.archive.crawler.restlet; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.LongRange; import org.archive.util.FileUtils; import org.restlet.data.CharacterSet; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Reference; import org.restlet.resource.CharacterRepresentation; import org.restlet.resource.FileRepresentation; /** * Representation wrapping a FileRepresentation, displaying its contents * in batches of lines at a time, with forward and backward navigation. * * @contributor gojomo */ public class PagedRepresentation extends CharacterRepresentation { // passed-in at construction /** wrapped FileRepresentation **/ protected FileRepresentation fileRepresentation; /** wrapped EnhDirectoryResource; used to formulate self-links **/ protected EnhDirectoryResource dirResource; /** position in file around which to fetch lines **/ protected long position; /** desired line count; negative to go back from position; default 128 **/ protected int lineCount; /** whether to display lines in reversed order (latest first) **/ protected boolean reversedOrder; // created when file is scanned /** text lines **/ protected List<String> lines; /** position range [start-of-first-line, past-end-of-last-line] in file **/ protected LongRange range; /** File **/ protected File file; // TODO: maybe, freeze length for more consistent display of growing files // (now, as length/%/bumper are written after lines retrieved, they // sometimes are indicative the file has grown before the page is // even rendered) public PagedRepresentation(FileRepresentation representation, EnhDirectoryResource resource, String pos, String lines, String reverse) { super(MediaType.TEXT_HTML); fileRepresentation = representation; dirResource = resource; position = StringUtils.isBlank(pos) ? 0 : Long.parseLong(pos); lineCount = StringUtils.isBlank(lines) ? 128 : Integer.parseInt(lines); reversedOrder = "y".equals(reverse); // TODO: remove if not necessary in future? setCharacterSet(CharacterSet.UTF_8); } @Override public Reader getReader() throws IOException { int estimatedSize = (Math.abs(lineCount) * 128) + 500; StringWriter writer = new StringWriter(estimatedSize); write(writer); return new StringReader(writer.toString()); } /** * Actually read the requested lines, and reverses if appropriate. * * If at file start, refuses to show fewer lines than are possible * ('bounces' against start). * * @throws IOException */ protected void loadLines() throws IOException { this.file = fileRepresentation.getFile(); this.lines = new LinkedList<String>(); this.range = FileUtils.pagedLines(file, position, lineCount, lines, 128); // bounce against the front of the file: don't show runt (fewer // lines than requested) unless absolutely necessary) if(lines.size()<Math.abs(lineCount) && range.getMinimumLong() == 0 && range.getMaximumLong()<file.length()) { this.lines = new LinkedList<String>(); this.range = FileUtils.pagedLines(file, 0, Math.abs(lineCount), lines, 128); } if(reversedOrder) { Collections.reverse(lines); } } /** * Write the paged HTML. * * @see org.restlet.resource.Representation#write(java.io.Writer) */ @Override public void write(Writer writer) throws IOException { loadLines(); PrintWriter pw = new PrintWriter(writer); pw.println("<b>Paged view:</b> "+file); emitControls(pw); pw.println("<pre>"); emitBumper(pw, true); for(String line : lines) { StringEscapeUtils.escapeHtml(pw,line); pw.println(); } emitBumper(pw, false); pw.println("</pre>"); emitControls(pw); pw.close(); } /** * Emit a "start" or "EOF" bumper as appropriate to prominently * indicate if page borders start- or end- of-file. * * @param pw PrintWriter * @param atTop boolean, true if at top of page */ protected void emitBumper(PrintWriter pw, boolean atTop) { if((!reversedOrder ^ atTop)&&(range.getMaximumLong()==file.length())) { pw.println("<span class='endBumper' style='font-weight:bold; color:white; background-color:#400'>«EOF»</span>"); return; } if((reversedOrder ^ atTop)&&(range.getMinimumLong()==0)) { pw.println("<span class='startBumper' style='font-weight:bold; color:white; background-color:#040'>«START»</span>"); } } /** * Emit the navigational controls. * * TODO: ugh! templatize, reduce duplication as possible * @param pw PrintWriter */ protected void emitControls(PrintWriter pw) { pw.println("<table id='controls' width='100%'><tr>"); if(reversedOrder) { pw.print("<td style='text-align:left'>"); pw.print("<a href='"); pw.print(getControlUri(-1,-Math.abs(lineCount),reversedOrder)); pw.println("'>« end</a>"); pw.print("<a href='"); pw.print(getControlUri( Math.min(file.length()-1, range.getMaximumLong()),Math.abs(lineCount),reversedOrder)); pw.println("'>‹ later</a>"); pw.println("bytes " +range.getMaximumLong() +"-"+range.getMinimumLong() +"/"+file.length() +" " +(int)(100*(range.getMaximumLong()/(float)file.length())) +"%"); pw.print("<a href='"); pw.print(getControlUri( Math.max(0, range.getMinimumLong()-1),-Math.abs(lineCount),reversedOrder)); pw.println("'>earlier ›</a>"); pw.print("<a href='"); pw.print(getControlUri(0,Math.abs(lineCount),reversedOrder)); pw.println("'>start »</a>"); pw.println("</td>"); pw.println("<td style='text-align:right'>"); pw.println("<a href='"+getControlUri(position,lineCount,false)+"'>forward</a>"); pw.println("| <b>reversed</b>"); } else { pw.print("<td style='text-align:left'>"); pw.print("<a href='"); pw.print(getControlUri(0,Math.abs(lineCount),reversedOrder)); pw.println("'>« start</a>"); pw.print("<a href='");pw.print(getControlUri( Math.max(0, range.getMinimumLong()-1),-Math.abs(lineCount),reversedOrder)); pw.println("'>‹ earlier</a>"); pw.println("bytes " +range.getMinimumLong() +"-"+range.getMaximumLong() +"/"+file.length() +" " +(int)(100*(range.getMaximumLong()/(float)file.length())) +"%"); pw.print("<a href='"); pw.print(getControlUri( Math.min(file.length()-1, range.getMaximumLong()),Math.abs(lineCount),reversedOrder)); pw.println("'>later ›</a>"); pw.print("<a href='"); pw.print(getControlUri(file.length(),-Math.abs(lineCount),reversedOrder)); pw.println("'>end »</a>"); pw.println("</td>"); pw.println("<td style='text-align:right'><b>forward</b>"); pw.println("| <a href='"+getControlUri(position,lineCount,true)+"'>reversed</a>"); } pw.print("<a href='"); pw.println(getControlUri(position,lineCount*2,reversedOrder)); pw.println("'> + </a>"); pw.println(lines.size()); pw.print("<a href='"+getControlUri(position,lineCount/2,reversedOrder)); pw.println("'> - </a> lines</td>"); pw.println("</tr></table>"); } /** * Construct navigational URI for given parameters. * * @param pos desired position in file * @param lines desired signed line count * @param reverse if line ordering should be displayed in reverse * @return String URI appropriate to navigate to desired view */ protected String getControlUri(long pos, int lines, boolean reverse) { Form query = new Form(); query.add("format","paged"); if(pos!=0) { query.add("pos", Long.toString(pos)); } if(lines!=128) { if(Math.abs(lines)<1) { lines = 1; } query.add("lines",Integer.toString(lines)); } if(reverse) { query.add("reverse","y"); } Reference viewRef = dirResource.getRequest().getOriginalRef().clone(); viewRef.setQuery(query.getQueryString()); return viewRef.toString(); } }