Line data Source code
1 : /*
2 : * Copyright (C) 2016-2018 Fulvio Benini
3 :
4 : * This file is part of Scid (Shane's Chess Information Database).
5 : *
6 : * Scid is free software: you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation.
9 : *
10 : * Scid is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with Scid. If not, see <http://www.gnu.org/licenses/>.
17 : *
18 : */
19 :
20 : /** @file
21 : * Implements the CodecPgn class, which manages the databases encoded in PGN
22 : * format.
23 : */
24 :
25 : #ifndef CODEC_PGN_H
26 : #define CODEC_PGN_H
27 :
28 : #include "codec_proxy.h"
29 : #include "common.h"
30 : #include "filebuf.h"
31 : #include "pgnparse.h"
32 : #include <algorithm>
33 : #include <cstring>
34 : #include <vector>
35 :
36 57 : class CodecPgn : public CodecProxy<CodecPgn> {
37 : Filebuf file_;
38 : std::streamsize fileSize_ = 0;
39 : std::string filename_;
40 : std::vector<char> buf_;
41 : size_t nParsed_ = 0;
42 : size_t nRead_ = 0;
43 : PgnParseLog parseLog_;
44 :
45 : public:
46 4 : Codec getType() const final { return ICodecDatabase::PGN; }
47 :
48 9 : std::vector<std::string> getFilenames() const final {
49 9 : return std::vector<std::string>(1, filename_);
50 : };
51 :
52 4 : errorT flush() final {
53 4 : errorT errFile = (file_.pubsync() == 0) ? OK : ERROR_FileWrite;
54 4 : errorT errProxy = CodecProxy<CodecPgn>::flush();
55 4 : return (errFile != OK) ? errFile : errProxy;
56 : }
57 :
58 : /**
59 : * Opens/creates a PGN database.
60 : * After successfully opening/creating the file, the object is ready for
61 : * parseNext() calls.
62 : * @param filename: full path of the pgn file to be opened.
63 : * @param fmode: valid file access mode.
64 : * @returns OK in case of success, an @e errorT code otherwise.
65 : */
66 19 : errorT open(const char* filename, fileModeT fmode) {
67 19 : ASSERT(filename && !file_.is_open());
68 19 : filename_ = filename;
69 19 : if (filename_.empty())
70 1 : return ERROR_FileOpen;
71 :
72 18 : errorT err_open = file_.Open(filename, fmode);
73 18 : if (err_open != OK)
74 1 : return err_open;
75 :
76 17 : buf_.resize(128 * 1024);
77 17 : nRead_ = nParsed_ = buf_.size();
78 17 : file_.pubsetbuf(nullptr, nRead_); // Optimization
79 :
80 17 : fileSize_ = file_.pubseekoff(0, std::ios::end);
81 17 : file_.pubseekpos(0);
82 17 : if (fileSize_ < 0)
83 0 : return ERROR_FileSeek;
84 :
85 17 : return OK;
86 : }
87 :
88 : /**
89 : * Reads the next game.
90 : * @param game: the Game object where the data will be stored.
91 : * @returns
92 : * - ERROR_NotFound if there are no more games to be read.
93 : * - OK otherwise.
94 : */
95 2025 : errorT parseNext(Game& game) {
96 2025 : const auto verge = 3 * (nRead_ / 4);
97 2025 : if (nParsed_ > verge && nRead_ == buf_.size()) {
98 256 : nParsed_ -= verge;
99 256 : nRead_ -= verge;
100 256 : std::copy_n(buf_.data() + verge, nRead_, buf_.data());
101 256 : nRead_ += file_.sgetn(buf_.data() + nRead_, verge);
102 : }
103 :
104 2025 : game.Clear();
105 4050 : PgnVisitor visitor(game);
106 : auto parse = pgn::parse_game(
107 2025 : {buf_.data() + nParsed_, buf_.data() + nRead_}, visitor);
108 :
109 2025 : bool eof = (nRead_ - nParsed_ == parse.first);
110 2025 : if (eof && nRead_ == buf_.size()) {
111 : // Reached the end of input, but the file contains more bytes.
112 2 : if (nRead_ <= 128 * 1024 * 1024) {
113 : // Double the buffer size and retry.
114 2 : buf_.resize(nRead_ * 2);
115 2 : nRead_ += file_.sgetn(buf_.data() + nRead_, nRead_);
116 2 : return parseNext(game);
117 : }
118 : // Abort
119 0 : nRead_ = nParsed_ = 0;
120 0 : parseLog_.log.append("PGN parsing aborted.\n");
121 0 : return ERROR_NotFound;
122 : }
123 :
124 2023 : nParsed_ += parse.first;
125 2023 : parseLog_.logGame(parse.first, visitor);
126 2023 : if (eof && !parse.second && *game.GetMoveComment() == '\0')
127 17 : return ERROR_NotFound;
128 :
129 2006 : return OK;
130 : }
131 :
132 : /**
133 : * Returns info about the parsing progress.
134 : * @returns a pair<size_t, size_t> where first element is the quantity of
135 : * data parsed and second one is the total amount of data of the database.
136 : */
137 17 : std::pair<size_t, size_t> parseProgress() {
138 17 : return std::make_pair(parseLog_.n_bytes / 1024, fileSize_ / 1024);
139 : }
140 :
141 : /**
142 : * Returns the list of errors produced by parseNext() calls.
143 : */
144 17 : const char* parseErrors() { return parseLog_.log.c_str(); }
145 :
146 : /**
147 : * Add a game into the database.
148 : * The @e game is encoded in pgn format and appended at the end of @e file_.
149 : * @param game: valid pointer to a Game object with the new data.
150 : * @returns OK in case of success, an @e errorT code otherwise.
151 : */
152 2000 : errorT gameAdd(Game* game) {
153 : // buf_.clear();
154 : // auto moves_begin = encode(*game, buf_);
155 : // Split the range (moves_begin, buf_.size()) into lines
156 : // auto sz = static_cast<std::streamsize>(buf_.size());
157 :
158 2000 : auto old_language = language;
159 2000 : language = 0;
160 2000 : game->SetPgnFormat(PGN_FORMAT_Plain);
161 2000 : game->ResetPgnStyle(PGN_STYLE_TAGS | PGN_STYLE_VARS |
162 : PGN_STYLE_COMMENTS | PGN_STYLE_SCIDFLAGS);
163 2000 : std::pair<const char*, unsigned> pgn = game->WriteToPGN(75, true);
164 2000 : language = old_language;
165 :
166 2000 : file_.pubseekpos(fileSize_);
167 2000 : if (file_.sputn(pgn.first, pgn.second) == pgn.second) {
168 2000 : fileSize_ += pgn.second;
169 2000 : return OK;
170 : }
171 0 : return ERROR_FileWrite;
172 : }
173 :
174 : /**
175 : * Encode a game into PGN format.
176 : * @param game: the Game object to encode.
177 : * @param dest: the container where the PGN Game will be appended.
178 : * @returns the size of the tag pairs section.
179 : */
180 : template <typename TCont> static size_t encode(Game& game, TCont& dest) {
181 : size_t tags_size = encodeTags(game, dest);
182 : dest.push_back('\n');
183 :
184 : game.MoveToStart();
185 : do {
186 : // TODO: comment, variations, etc..
187 : const char* next_move = game.GetNextSAN();
188 : dest.insert(dest.end(), next_move,
189 : next_move + std::strlen(next_move));
190 : dest.push_back(' ');
191 : } while (game.MoveForwardInPGN() == OK);
192 :
193 : return tags_size;
194 : }
195 :
196 : template <typename TCont>
197 : static size_t encodeTags(Game& game, TCont& dest) {
198 : auto format_tag = [&dest](const char* tag, const char* value) {
199 : dest.push_back('[');
200 : dest.insert(dest.end(), tag, tag + std::strlen(tag));
201 : dest.push_back(' ');
202 :
203 : dest.push_back('"');
204 : auto value_begin = dest.size();
205 : dest.insert(dest.end(), value, value + std::strlen(value));
206 : pgn::escape_string(dest, value_begin);
207 : dest.push_back('"');
208 :
209 : dest.push_back(']');
210 : dest.push_back('\n');
211 : };
212 : auto format_tag_question_mark = [&format_tag](const char* tag,
213 : const char* value) {
214 : format_tag(tag, (*value) ? value : "?");
215 : };
216 :
217 : size_t tags_size = dest.size();
218 : gamevisit::tags_STR(game, format_tag_question_mark);
219 : gamevisit::tags_extra(game, format_tag);
220 : return dest.size() - tags_size;
221 : }
222 : };
223 :
224 : #endif
|