001package votorola.a.diff; // Copyright 2010-2013, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import java.io.*; 004import java.net.*; 005import java.util.*; 006import java.util.regex.*; 007import votorola.a.*; 008import votorola.a.position.*; 009import votorola.g.hold.*; 010import votorola.g.io.*; 011import votorola.g.lang.*; 012import votorola.g.logging.*; 013 014 015/** A directory of cached diff output <code>~/votorola/in/diff</code>. Cache files are 016 * created with broad permissions for writing by all (vote-server account, Tomcat and 017 * other process owners). Administrators should avoid deleting files at runtime, or 018 * temporary process errors may occur. 019 * 020 * @see <a href='http://en.wikipedia.org/wiki/Diff' 021 * >en.wikipedia.org/wiki/Diff</a> 022 * @see <a href='http://www.gnu.org/software/diffutils/manual/diff.html' 023 * >www.gnu.org/software/diffutils/manual/diff.html</a> 024 */ 025public final @ThreadSafe class DiffCache extends File 026{ 027 028 029 /** Constructs a DiffCache for inclusion in a vote-server. 030 * 031 * @see VoteServer#diffCache() 032 */ 033 public DiffCache( VoteServer _vS, final VoteServer.ConstructionContext cc ) throws IOException 034 { 035 super( _vS.inDirectory(), "diff" ); 036 vS = _vS; 037 if( cc == null ) throw new NullPointerException(); 038 // cc is unused except to preclude redundant construction external to vote-server 039 040 if( !exists() ) 041 { 042 if( !mkdir() ) throw new IOException( "unable to create directory: " + DiffCache.this ); 043 044 setWritable( true, /*ownerOnly*/false ); 045 } 046 } 047 048 049 050 // ------------------------------------------------------------------------------------ 051 052 053 /** Returns the cached diff output for the specified draft pair. If the output is not 054 * yet cached, then it is generated anew and cached for future calls. 055 */ 056 public File diffFile( final DraftPair pair ) throws IOException 057 { 058 final File diffFile = new File( DiffCache.this, pair.diffKeyParse().key() ); 059 if( !diffFile.isFile() ) 060 { 061 final DraftRevision aDraft = pair.aCore().draft(); 062 final DraftRevision bDraft = pair.bCore().draft(); 063 createDiffFile( diffFile, aDraft.wikiScriptURI(), aDraft.rev(), 064 bDraft.wikiScriptURI(), bDraft.rev() ); 065 } 066 return diffFile; 067 } 068 069 070 071 /** The line transformer (LT) for the diffs of this wiki cache. It partly determines 072 * the structure of each diff (D) and is equally tied to the diff identification 073 * scheme (ID-D-LT). ID maps a diff URL (and cache filename) to a D in a stable 074 * manner, such that a reader following a URL that was embedded in an archived 075 * discussion will see the same D as the original conversants. Any change of 076 * transformer (LT') that would substantially alter a diff (D') will therefore be 077 * deployed under a new identification scheme (ID'). 078 */ 079 public static final LineTransformer1 LINE_TRANSFORMER = new LineTransformer1(); 080 081 082 083 /** The pattern of a negative 'diff' result, where the two files are identical. 084 */ 085 public static final Pattern NO_DIFF_PATTERN = Pattern.compile( 086 "^Files .+ and .+ are identical$" ); // cf. s.gwt.mediawiki.DifferenceShadowsV.NO_DIFF_PATTERN 087 088 089 090//// P r i v a t e /////////////////////////////////////////////////////////////////////// 091 092 093 private File createDiffFile( final File diffFile, 094 final URI aWikiScriptLocation, final int aRev, 095 final URI bWikiScriptLocation, final int bRev ) throws IOException 096 { 097 final Spool fileSpool = new Spool1(); 098 final File aFile = LINE_TRANSFORMER.fetchPageAsFile( aWikiScriptLocation, "oldid", 099 aRev, "DiffCache_a", fileSpool )[1]; 100 final File bFile = LINE_TRANSFORMER.fetchPageAsFile( bWikiScriptLocation, "oldid", 101 bRev, "DiffCache_b", fileSpool )[1]; 102 final ProcessBuilder pB = new ProcessBuilder( 103 "/bin/sh", "-c", "diff --report-identical-files --unified '" + aFile.getName() + "' '" 104 + bFile.getName() + "'" ); 105 pB.directory( aFile.getParentFile() ); 106 LoggerX.i(getClass()).config( "from directory " + pB.directory() + ", calling out to OS: " + pB.command() ); 107 108 final File diffTmpFile = File.createTempFile( "DiffCache", "." + diffFile.getName() ); 109 // try 110 // { 111 final Process p = pB.start(); 112 final InputStream in = new BufferedInputStream( p.getInputStream() ); 113 try 114 { 115 final OutputStream out = new BufferedOutputStream( new FileOutputStream( 116 diffTmpFile )); 117 try 118 { 119 for( ;; ) { int bb = in.read(); if( bb == -1 ) break; out.write( bb ); } 120 } 121 finally{ out.close(); } 122 } 123 finally{ in.close(); } 124 125 final int exitValue = ProcessX.waitForWithoutInterrupt( p ); 126 if( exitValue < /*no difference*/0 || exitValue > /*difference*/1 ) throw new IOException( "exit value of " + exitValue + " from process: " + pB.command() ); 127 128 fileSpool.unwind(); // if no exceptions, otherwise keep files for admin to diagnose 129 diffTmpFile.setWritable( true, /*ownerOnly*/false ); // before moving to cache 130 // if( !FileX.renameFrom( diffTmpFile, diffFile )) throw new IOException( "unable to rename file: " + diffTmpFile + " -> " + diffFile ); 131 /// but mysteriously failing for my voff, though all permissions seem correct 132 // FileX.renameFromDefaultsToCopy( diffTmpFile, diffFile ); 133 /// Java copy does not allow for preservation of the writable permissions as set above 134 FileX.renameFromDefaultToMv( diffTmpFile, diffFile ); 135 // } 136 // finally{ if( diffTmpFile.isFile() ) diffTmpFile.delete(); } // clean up from exception or failure 137 return diffFile; 138 } 139 140 141 142 /** Returns the cached diff output for the specified difference key. If the output is 143 * not yet cached, then it is generated anew and cached for future calls. Generation 144 * may entail constructing a draft pair; if you already have one constructed, then 145 * use {@linkplain #diffFile(DraftPair) diffFile}(pair) instead. 146 * 147 * @see DiffKey 148 */ 149 private File diffFile( final String key ) throws IOException 150 { 151 final File diffFile = new File( DiffCache.this, key ); 152 if( !diffFile.isFile() ) 153 { 154 final DiffKeyParse parse = new DiffKeyParse( key ); 155 final List<Integer> aPath = parse.aPath(); 156 final List<Integer> bPath = parse.bPath(); 157 final int aRev; 158 final int bRev; 159 final URI aWikiScriptLocation; 160 final URI bWikiScriptLocation; 161 if( aPath.size() == 1 && bPath.size() == 1 ) 162 { 163 aRev = aPath.get( 0 ); 164 bRev = bPath.get( 0 ); 165 aWikiScriptLocation = bWikiScriptLocation = vS.pollwiki().scriptURI(); 166 } 167 else 168 { 169 final DraftPair pair = DraftPair.newDraftPair( parse, vS.pollwiki() ); 170 final DraftRevision aDraft = pair.aCore().draft(); 171 final DraftRevision bDraft = pair.bCore().draft(); 172 aRev = aDraft.rev(); 173 bRev = bDraft.rev(); 174 aWikiScriptLocation = aDraft.wikiScriptURI(); 175 bWikiScriptLocation = bDraft.wikiScriptURI(); 176 } 177 createDiffFile( diffFile, aWikiScriptLocation, aRev, bWikiScriptLocation, bRev ); 178 } 179 return diffFile; 180 } 181 182 183 184 private final VoteServer vS; 185 186 187}