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