LCOV - code coverage report
Current view: top level - src - codec_pgn.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 58 63 92.1 %
Date: 2019-01-29 11:06:41 Functions: 11 11 100.0 %

          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

Generated by: LCOV version 1.13