1 // ResultFile.java 2 3 package net.sf.gogui.tools.twogtp; 4 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileNotFoundException; 8 import java.io.FileOutputStream; 9 import java.io.IOException; 10 import java.io.OutputStream; 11 import java.io.RandomAccessFile; 12 import java.nio.channels.FileChannel; 13 import java.nio.channels.FileLock; 14 import java.text.NumberFormat; 15 import java.util.ArrayList; 16 import java.util.TreeMap; 17 import java.util.TreeSet; 18 import net.sf.gogui.game.ConstNode; 19 import net.sf.gogui.game.ConstGame; 20 import net.sf.gogui.go.Komi; 21 import net.sf.gogui.sgf.SgfError; 22 import net.sf.gogui.sgf.SgfReader; 23 import net.sf.gogui.sgf.SgfWriter; 24 import net.sf.gogui.util.ErrorMessage; 25 import net.sf.gogui.util.Platform; 26 import net.sf.gogui.util.StringUtil; 27 import net.sf.gogui.util.Table; 28 import net.sf.gogui.xml.XmlWriter; 29 import net.sf.gogui.version.Version; 30 31 public class ResultFile 32 { ResultFile(boolean force, Program black, Program white, Program referee, int numberGames, int size, Komi komi, String filePrefix, Openings openings, boolean alternate, boolean useXml, int numberThreads)33 public ResultFile(boolean force, Program black, Program white, 34 Program referee, int numberGames, int size, Komi komi, 35 String filePrefix, Openings openings, boolean alternate, 36 boolean useXml, int numberThreads) throws ErrorMessage 37 { 38 m_filePrefix = filePrefix; 39 m_alternate = alternate; 40 m_numberGames = numberGames; 41 m_useXml = useXml; 42 m_numberThreads = numberThreads; 43 m_lockFile = new File(filePrefix + ".lock"); 44 acquireLock(); 45 m_tableFile = new File(filePrefix + ".dat"); 46 if (force) 47 { 48 if (m_tableFile.exists() && ! m_tableFile.delete()) 49 throw new ErrorMessage("Could not delete file '" 50 + m_tableFile + "'"); 51 } 52 if (m_tableFile.exists()) 53 { 54 m_table = readTable(m_tableFile, numberGames, m_gameExists); 55 m_nextGameIndex = 0; 56 while (m_gameExists.contains(m_nextGameIndex)) 57 { 58 ++m_nextGameIndex; 59 if (numberGames > 0 && m_nextGameIndex > numberGames) 60 { 61 m_nextGameIndex = -1; 62 break; 63 } 64 } 65 readGames(); 66 } 67 else 68 { 69 m_table = createTable(black, white, referee, size, komi, openings); 70 m_nextGameIndex = 0; 71 } 72 } 73 addResult(int gameIndex, ConstGame game, String resultBlack, String resultWhite, String resultReferee, boolean alternated, int numberMoves, boolean error, String errorMessage, double timeBlack, double timeWhite, double cpuTimeBlack, double cpuTimeWhite)74 public synchronized void addResult(int gameIndex, ConstGame game, 75 String resultBlack, String resultWhite, 76 String resultReferee, 77 boolean alternated, int numberMoves, 78 boolean error, String errorMessage, 79 double timeBlack, double timeWhite, 80 double cpuTimeBlack, 81 double cpuTimeWhite) 82 throws ErrorMessage 83 { 84 ArrayList<Compare.Placement> moves 85 = Compare.getPlacements(game.getTree().getRootConst()); 86 String duplicate = 87 Compare.checkDuplicate(game.getBoard(), moves, m_games, 88 m_alternate, alternated); 89 NumberFormat format = StringUtil.getNumberFormat(1); 90 m_table.startRow(); 91 m_table.set("GAME", Integer.toString(gameIndex)); 92 m_table.set("RES_B", resultBlack); 93 m_table.set("RES_W", resultWhite); 94 m_table.set("RES_R", resultReferee); 95 m_table.set("ALT", alternated ? "1" : "0"); 96 m_table.set("DUP", duplicate); 97 m_table.set("LEN", numberMoves); 98 m_table.set("TIME_B", format.format(timeBlack)); 99 m_table.set("TIME_W", format.format(timeWhite)); 100 m_table.set("CPU_B", format.format(cpuTimeBlack)); 101 m_table.set("CPU_W", format.format(cpuTimeWhite)); 102 m_table.set("ERR", error ? "1" : "0"); 103 m_table.set("ERR_MSG", errorMessage); 104 105 // The code does not rely on the table being sorted by game number, 106 // but it looks nicer for the user. 107 int rowEnd = m_table.getNumberRows(); 108 int rowBegin = rowEnd - m_numberThreads; 109 if (rowBegin < 0) 110 rowBegin = 0; 111 // If the run was terminated and continued with a different number 112 // of threads, there could be a gap between gameIndex and 113 // getNumberRows() larger than m_numberThreads 114 if (gameIndex < rowBegin) 115 rowBegin = gameIndex; 116 m_table.sortByIntColumn("GAME", rowBegin, rowEnd); 117 118 File tmpFile = new File(m_tableFile.getAbsolutePath() + ".new"); 119 try 120 { 121 m_table.save(tmpFile); 122 if (Platform.isWindows()) 123 // File.renameTo() fails on Windows if target exists 124 m_tableFile.delete(); 125 tmpFile.renameTo(m_tableFile); 126 } 127 catch (IOException e) 128 { 129 throw new ErrorMessage("Could not write to: " + m_tableFile); 130 } 131 File file = getFile(gameIndex); 132 try 133 { 134 OutputStream out = new FileOutputStream(file); 135 if (m_useXml) 136 new XmlWriter(out, game.getTree(), 137 "gogui-twogtp:" + Version.get()); 138 else 139 new SgfWriter(out, game.getTree(), 140 "gogui-twogtp", Version.get()); 141 m_games.put(gameIndex, moves); 142 } 143 catch (FileNotFoundException e) 144 { 145 throw new ErrorMessage("Could not save " + file + ": " 146 + e.getMessage()); 147 } 148 } 149 close()150 public void close() 151 { 152 try 153 { 154 m_lockFileChannel.close(); 155 } 156 catch (IOException e) 157 { 158 System.err.println("Could not close '" + m_lockFile + "'"); 159 } 160 if (! m_lockFile.delete()) 161 System.err.println("Could not delete '" + m_lockFile + "'"); 162 } 163 getNextGameIndex()164 public synchronized int getNextGameIndex() 165 { 166 if (m_nextGameIndex != -1) 167 while (m_gameExists.contains(m_nextGameIndex)) 168 { 169 ++m_nextGameIndex; 170 if (m_numberGames > 0 && m_nextGameIndex >= m_numberGames) 171 { 172 m_nextGameIndex = -1; 173 break; 174 } 175 } 176 if (m_nextGameIndex != -1) 177 m_gameExists.add(m_nextGameIndex); 178 return m_nextGameIndex; 179 } 180 181 private final boolean m_alternate; 182 183 private final boolean m_useXml; 184 185 private final TreeSet<Integer> m_gameExists = new TreeSet<Integer>(); 186 187 private int m_nextGameIndex; 188 189 private final int m_numberGames; 190 191 private final int m_numberThreads; 192 193 private final String m_filePrefix; 194 195 private final File m_tableFile; 196 197 private final File m_lockFile; 198 199 private FileChannel m_lockFileChannel; 200 201 private final Table m_table; 202 203 private final TreeMap<Integer, ArrayList<Compare.Placement>> m_games 204 = new TreeMap<Integer, ArrayList<Compare.Placement>>(); 205 acquireLock()206 private void acquireLock() throws ErrorMessage 207 { 208 try 209 { 210 m_lockFile.createNewFile(); 211 m_lockFileChannel 212 = new RandomAccessFile(m_lockFile, "rw").getChannel(); 213 FileLock lock = m_lockFileChannel.tryLock(); 214 if (lock == null) 215 throw new ErrorMessage("Could not get lock on file '" 216 + m_lockFile 217 + "': already used by another instance of TwoGtp"); 218 } 219 catch (IOException e) 220 { 221 throw new ErrorMessage("Could not lock file '" + m_lockFile 222 + "': " + e.getMessage()); 223 } 224 } 225 createTable(Program black, Program white, Program referee, int size, Komi komi, Openings openings)226 private Table createTable(Program black, Program white, Program referee, 227 int size, Komi komi, Openings openings) 228 { 229 ArrayList<String> columns = new ArrayList<String>(); 230 columns.add("GAME"); 231 columns.add("RES_B"); 232 columns.add("RES_W"); 233 columns.add("RES_R"); 234 columns.add("ALT"); 235 columns.add("DUP"); 236 columns.add("LEN"); 237 columns.add("TIME_B"); 238 columns.add("TIME_W"); 239 columns.add("CPU_B"); 240 columns.add("CPU_W"); 241 columns.add("ERR"); 242 columns.add("ERR_MSG"); 243 Table table = new Table(columns); 244 black.setTableProperties(table); 245 white.setTableProperties(table); 246 if (referee == null) 247 table.setProperty("Referee", "-"); 248 else 249 referee.setTableProperties(table); 250 table.setProperty("Size", Integer.toString(size)); 251 table.setProperty("Komi", komi.toString()); 252 if (openings != null) 253 table.setProperty("Openings", 254 openings.getDirectory() + " (" 255 + openings.getNumber() + " files)"); 256 table.setProperty("Date", StringUtil.getDate()); 257 table.setProperty("Host", Platform.getHostInfo()); 258 table.setProperty("Xml", m_useXml ? "1" : "0"); 259 return table; 260 } 261 getFile(int gameIndex)262 private File getFile(int gameIndex) 263 { 264 if (m_useXml) 265 return new File(m_filePrefix + "-" + gameIndex + ".xml"); 266 else 267 return new File(m_filePrefix + "-" + gameIndex + ".sgf"); 268 } 269 readGames()270 private void readGames() 271 { 272 for (int n = 0; n < m_numberGames; ++n) 273 { 274 if (! m_gameExists.contains(n)) 275 continue; 276 File file = getFile(n); 277 if (! file.exists()) 278 { 279 System.err.println("Game " + file + " not found"); 280 continue; 281 } 282 if (! file.exists()) 283 return; 284 try 285 { 286 FileInputStream fileStream = new FileInputStream(file); 287 SgfReader reader = new SgfReader(fileStream, file, null, 0); 288 ConstNode root = reader.getTree().getRoot(); 289 m_games.put(n, Compare.getPlacements(root)); 290 } 291 catch (SgfError e) 292 { 293 System.err.println("Error reading " + file + ": " + 294 e.getMessage()); 295 } 296 catch (Exception e) 297 { 298 System.err.println("Error reading " + file + ": " + 299 e.getMessage()); 300 } 301 } 302 } 303 readTable(File file, int numberGames, TreeSet<Integer> gameExists)304 private static Table readTable(File file, int numberGames, 305 TreeSet<Integer> gameExists) 306 throws ErrorMessage 307 { 308 Table table = new Table(); 309 try 310 { 311 table.read(file); 312 int numberRows = table.getNumberRows(); 313 if (numberGames > 0 && numberRows >= numberGames) 314 throw new ErrorMessage("File " + file + " already contains " 315 + numberRows + " games"); 316 for (int i = 0; i < numberRows; ++i) 317 { 318 int gameIndex = Integer.parseInt(table.get("GAME", i)); 319 if (gameIndex < 0) 320 throw new ErrorMessage("Invalid file format: " + file); 321 gameExists.add(gameIndex); 322 } 323 } 324 catch (NumberFormatException e) 325 { 326 throw new ErrorMessage("Invalid file format: " + file); 327 } 328 catch (FileNotFoundException e) 329 { 330 throw new ErrorMessage(e.getMessage()); 331 } 332 catch (IOException e) 333 { 334 throw new ErrorMessage("Read error: " + file); 335 } 336 return table; 337 } 338 } 339