Line data Source code
1 : /*
2 : * Copyright (C) 2014-2016 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 : #include "scidbase.h"
20 : #include "codec_memory.h"
21 : #include "codec_pgn.h"
22 : #include "codec_scid4.h"
23 : #include "common.h"
24 : #include "sortcache.h"
25 : #include "stored.h"
26 : #include <algorithm>
27 : #include <math.h>
28 :
29 : std::pair<ICodecDatabase*, errorT>
30 50 : ICodecDatabase::open(Codec codec, fileModeT fMode, const char* filename,
31 : const Progress& progress, Index* idx, NameBase* nb) {
32 50 : auto createCodec = [](auto codec) -> ICodecDatabase* {
33 50 : switch (codec) {
34 15 : case ICodecDatabase::MEMORY:
35 15 : return new CodecMemory();
36 16 : case ICodecDatabase::SCID4:
37 16 : return new CodecSCID4();
38 19 : case ICodecDatabase::PGN:
39 19 : return new CodecPgn();
40 : }
41 0 : ASSERT(0);
42 : return nullptr;
43 : };
44 :
45 50 : auto obj = createCodec(codec);
46 50 : auto err = obj->dyn_open(fMode, filename, progress, idx, nb);
47 50 : if (err != OK && err != ERROR_NameDataLoss) {
48 9 : delete obj;
49 9 : obj = nullptr;
50 : }
51 50 : return {obj, err};
52 : }
53 :
54 14 : scidBaseT::scidBaseT() {
55 14 : idx = new Index;
56 14 : nb_ = new NameBase;
57 14 : game = new Game;
58 14 : gameNumber = -1;
59 14 : gameAltered = false;
60 14 : inUse = false;
61 14 : tree.moveCount = tree.totalCount = 0;
62 14 : fileMode_ = FMODE_None;
63 14 : bbuf = new ByteBuffer(BBUF_SIZE);
64 14 : dbFilter = new Filter(0);
65 14 : treeFilter = new Filter(0);
66 14 : duplicates_ = NULL;
67 14 : stats_ = NULL;
68 14 : }
69 :
70 28 : scidBaseT::~scidBaseT() {
71 14 : if (inUse)
72 14 : Close();
73 :
74 14 : delete[] duplicates_;
75 14 : delete idx;
76 14 : delete nb_;
77 14 : delete game;
78 14 : delete bbuf;
79 14 : delete stats_;
80 14 : delete dbFilter;
81 14 : delete treeFilter;
82 14 : }
83 :
84 14 : errorT scidBaseT::Open(ICodecDatabase::Codec dbtype, fileModeT fMode,
85 : const char* filename, const Progress& progress) {
86 14 : if (inUse)
87 0 : return ERROR_FileInUse;
88 14 : if (filename == 0)
89 7 : filename = "";
90 :
91 14 : auto obj = ICodecDatabase::open(dbtype, fMode, filename, progress, idx, nb_);
92 14 : if (obj.first) {
93 14 : codec_.reset(obj.first);
94 14 : inUse = true;
95 14 : fileMode_ = (fMode == FMODE_Create) ? FMODE_Both : fMode;
96 14 : fileName_ = filename;
97 14 : gameNumber = -1;
98 :
99 : // Initialize the filters: all the games are included by default.
100 14 : dbFilter->Init(numGames());
101 14 : treeFilter->Init(numGames());
102 14 : ASSERT(filters_.empty());
103 :
104 : // Default treeCache size: 250
105 14 : treeCache.CacheResize(250);
106 : } else {
107 0 : idx->Close();
108 0 : nb_->Clear();
109 : }
110 :
111 14 : return obj.second;
112 : }
113 :
114 14 : errorT scidBaseT::Close () {
115 14 : ASSERT(inUse);
116 :
117 16 : for (auto& sortCache : sortCaches_) {
118 2 : delete sortCache.second;
119 : }
120 14 : sortCaches_.clear();
121 :
122 14 : errorT errIdx = idx->Close();
123 14 : nb_->Clear();
124 14 : codec_ = nullptr;
125 :
126 14 : clear();
127 14 : game->Clear();
128 14 : fileMode_ = FMODE_None;
129 14 : fileName_ = "<empty>";
130 14 : gameNumber = -1;
131 14 : gameAltered = false;
132 14 : dbFilter->Init(0);
133 14 : treeFilter->Init(0);
134 14 : for (size_t i=0, n = filters_.size(); i < n; i++) delete filters_[i].second;
135 14 : filters_.clear();
136 14 : inUse = false;
137 :
138 14 : return errIdx;
139 : }
140 :
141 :
142 25 : void scidBaseT::clear() {
143 25 : if (stats_ != NULL) { delete stats_; stats_ = NULL;}
144 25 : if (duplicates_ != NULL) { delete[] duplicates_; duplicates_ = NULL; }
145 25 : treeCache.Clear();
146 125 : for (nameT nt = NAME_PLAYER; nt < NUM_NAME_TYPES; nt++) {
147 100 : nameFreq_[nt].resize(0);
148 : }
149 25 : }
150 :
151 11 : void scidBaseT::beginTransaction() {
152 11 : for (auto& sortCache : sortCaches_) {
153 0 : sortCache.second->prepareForChanges();
154 : }
155 11 : }
156 :
157 11 : errorT scidBaseT::endTransaction(gamenumT gNum) {
158 11 : clear();
159 11 : errorT res = codec_->flush();
160 :
161 11 : auto n_games = numGames();
162 11 : if (dbFilter->Size() != n_games) {
163 10 : dbFilter->Resize(n_games);
164 10 : treeFilter->Resize(n_games);
165 10 : for (auto& filter : filters_) {
166 0 : filter.second->Resize(n_games);
167 : }
168 : }
169 :
170 11 : for (auto& sortCache : sortCaches_) {
171 0 : sortCache.second->checkForChanges(gNum);
172 : }
173 :
174 11 : return res;
175 : }
176 :
177 9 : errorT scidBaseT::saveGame(Game* game, gamenumT replacedGameId) {
178 9 : beginTransaction();
179 9 : errorT err1 = saveGameHelper(game, replacedGameId);
180 9 : errorT err2 = endTransaction(replacedGameId);
181 9 : return (err1 != OK) ? err1 : err2;
182 : }
183 :
184 9 : errorT scidBaseT::saveGameHelper(Game* game, gamenumT gameId) {
185 9 : if (isReadOnly())
186 0 : return ERROR_FileReadOnly;
187 :
188 9 : if (gameId < numGames())
189 1 : return codec_->saveGame(game, gameId);
190 :
191 8 : return codec_->addGame(game);
192 : }
193 :
194 3 : errorT scidBaseT::importGame(const scidBaseT* srcBase, uint gNum) {
195 3 : if (srcBase == this) return ERROR_BadArg;
196 2 : if (isReadOnly()) return ERROR_FileReadOnly;
197 2 : if (gNum >= srcBase->numGames()) return ERROR_BadArg;
198 :
199 1 : beginTransaction();
200 1 : errorT err = importGameHelper(srcBase, gNum);
201 1 : errorT errClear = endTransaction();
202 1 : return (err == OK) ? errClear : err;
203 : }
204 :
205 2 : errorT scidBaseT::importGames(const scidBaseT* srcBase, const HFilter& filter, const Progress& progress) {
206 2 : ASSERT(srcBase != 0);
207 2 : ASSERT(filter != 0);
208 2 : if (srcBase == this) return ERROR_BadArg;
209 1 : if (isReadOnly()) return ERROR_FileReadOnly;
210 :
211 1 : beginTransaction();
212 1 : errorT err = OK;
213 1 : size_t iProgress = 0;
214 1 : size_t totGames = filter->size();
215 4 : for (gamenumT gNum = 0, n = srcBase->numGames(); gNum < n; gNum++) {
216 3 : if (filter.get(gNum) == 0) continue;
217 2 : err = importGameHelper(srcBase, gNum);
218 2 : if (err != OK) break;
219 2 : if (++iProgress % 8192 == 0) {
220 0 : if (!progress.report(iProgress, totGames)) break;
221 : }
222 : }
223 1 : errorT errClear = endTransaction();
224 1 : return (err == OK) ? errClear : err;
225 : }
226 :
227 3 : errorT scidBaseT::importGameHelper(const scidBaseT* srcBase, gamenumT gNum) {
228 3 : auto srcIe = srcBase->getIndexEntry(gNum);
229 3 : auto dataSz = srcIe->GetLength();
230 3 : auto data = srcBase->codec_->getGameData(srcIe->GetOffset(), dataSz);
231 3 : if (data == nullptr)
232 0 : return ERROR_FileRead;
233 :
234 3 : return codec_->addGame(srcIe, srcBase->getNameBase(), data, dataSz);
235 : }
236 :
237 0 : errorT scidBaseT::importGames(ICodecDatabase::Codec dbtype,
238 : const char* filename, const Progress& progress,
239 : std::string& errorMsg) {
240 0 : ASSERT(dbtype == ICodecDatabase::PGN);
241 :
242 0 : if (isReadOnly())
243 0 : return ERROR_FileReadOnly;
244 :
245 0 : beginTransaction();
246 :
247 0 : CodecPgn pgn;
248 0 : auto res = pgn.open(filename, FMODE_ReadOnly);
249 0 : if (res == OK) {
250 0 : res = CodecPgn::parseGames(
251 0 : progress, pgn, [&](Game& game) { return codec_->addGame(&game); });
252 0 : errorMsg = pgn.parseErrors();
253 : }
254 :
255 0 : auto res_endTrans = endTransaction();
256 0 : return (res != OK) ? res : res_endTrans;
257 : }
258 :
259 : /**
260 : * Filters
261 : */
262 102 : std::string scidBaseT::newFilter() {
263 102 : std::string newname = (filters_.size() == 0)
264 : ? "a_"
265 102 : : filters_.back().first;
266 102 : if (newname[0] == 'z') {
267 3 : newname = 'a' + newname;
268 : } else {
269 99 : newname = ++(newname[0]) + newname.substr(1);
270 : }
271 102 : filters_.push_back(std::make_pair(newname, new Filter(numGames())));
272 102 : return newname;
273 : }
274 :
275 17395 : std::string scidBaseT::composeFilter(const std::string& mainFilter,
276 : const std::string& maskFilter) const {
277 17395 : std::string res;
278 17395 : if (mainFilter.empty()) return res;
279 :
280 17265 : if (mainFilter[0] != '+') {
281 8470 : res = mainFilter;
282 : } else {
283 8795 : size_t maskName = mainFilter.find('+', 1);
284 8795 : if (maskName != std::string::npos)
285 8405 : res = mainFilter.substr(1, maskName - 1);
286 : }
287 :
288 17265 : if (!maskFilter.empty()) {
289 9120 : res = '+' + res + "+" + maskFilter;
290 : }
291 :
292 17265 : if (getFilter(res) == 0) res.clear();
293 17265 : return res;
294 : }
295 :
296 29 : void scidBaseT::deleteFilter(const char* filterId) {
297 1404 : for (size_t i = 0, n = filters_.size(); i < n; i++) {
298 1400 : if (filters_[i].first == filterId) {
299 25 : delete filters_[i].second;
300 25 : filters_.erase(filters_.begin() + i);
301 25 : break;
302 : }
303 : }
304 29 : }
305 :
306 53686 : Filter* scidBaseT::fetchFilter(const std::string& filterId) const {
307 53686 : if (filterId == "dbfilter") return dbFilter;
308 53162 : if (filterId == "tree") return treeFilter;
309 :
310 2084108 : for (size_t i = 0, n = filters_.size(); i < n; i++) {
311 2078453 : if (filterId == filters_[i].first)
312 45819 : return filters_[i].second;
313 : }
314 5655 : return 0;
315 : }
316 :
317 36551 : HFilter scidBaseT::getFilterHelper(const std::string& filterId,
318 : bool unmasked) const {
319 36551 : Filter* main = 0;
320 36551 : const Filter* mask = 0;
321 36551 : if (filterId.empty() || filterId[0] != '+') {
322 18636 : main = fetchFilter(filterId);
323 : } else {
324 17915 : size_t maskName = filterId.find('+', 1);
325 17915 : if (maskName != std::string::npos) {
326 17525 : main = fetchFilter(filterId.substr(1, maskName - 1));
327 17525 : if (!unmasked) mask = fetchFilter(filterId.substr(maskName + 1));
328 : }
329 : }
330 36551 : return HFilter(main, mask);
331 : }
332 :
333 : /**
334 : * Statistics
335 : */
336 0 : const scidBaseT::Stats& scidBaseT::getStats() const {
337 0 : if (stats_ == NULL) stats_ = new scidBaseT::Stats(this);
338 0 : return *stats_;
339 : }
340 :
341 0 : scidBaseT::Stats::Eco::Eco()
342 0 : : count(0) {
343 0 : std::fill_n(results, NUM_RESULT_TYPES, 0);
344 0 : }
345 :
346 0 : scidBaseT::Stats::Stats(const scidBaseT* dbase) {
347 0 : std::fill(flagCount, flagCount + IndexEntry::IDX_NUM_FLAGS, 0);
348 0 : minDate = ZERO_DATE;
349 0 : maxDate = ZERO_DATE;
350 0 : nYears = 0;
351 0 : sumYears = 0;
352 0 : std::fill_n(nResults, NUM_RESULT_TYPES, 0);
353 0 : nRatings = 0;
354 0 : sumRatings = 0;
355 0 : minRating = 0;
356 0 : maxRating = 0;
357 :
358 : // Read stats from index entry of each game:
359 0 : for (gamenumT gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
360 0 : const IndexEntry* ie = dbase->getIndexEntry(gnum);
361 0 : nResults[ie->GetResult()]++;
362 0 : eloT elo = ie->GetWhiteElo();
363 0 : if (elo > 0) {
364 0 : nRatings++;
365 0 : sumRatings += elo;
366 0 : if (minRating == 0) { minRating = elo; }
367 0 : if (elo < minRating) { minRating = elo; }
368 0 : if (elo > maxRating) { maxRating = elo; }
369 : }
370 0 : elo = ie->GetBlackElo();
371 0 : if (elo > 0) {
372 0 : nRatings++;
373 0 : sumRatings += elo;
374 0 : if (minRating == 0) { minRating = elo; }
375 0 : if (elo < minRating) { minRating = elo; }
376 0 : if (elo > maxRating) { maxRating = elo; }
377 : }
378 0 : dateT date = ie->GetDate();
379 0 : if (gnum == 0) {
380 0 : maxDate = minDate = date;
381 : }
382 0 : if (date_GetYear(date) > 0) {
383 0 : if (date < minDate) { minDate = date; }
384 0 : if (date > maxDate) { maxDate = date; }
385 0 : nYears++;
386 0 : sumYears += date_GetYear (date);
387 : }
388 :
389 0 : for (uint flag = 0; flag < IndexEntry::IDX_NUM_FLAGS; flag++) {
390 0 : bool value = ie->GetFlag (1 << flag);
391 0 : if (value) {
392 0 : flagCount[flag]++;
393 : }
394 : }
395 :
396 0 : resultT result = ie->GetResult();
397 0 : ecoT eco = ie->GetEcoCode();
398 0 : if (eco == 0) {
399 0 : ecoEmpty_.count++;
400 0 : ecoEmpty_.results[result]++;
401 : } else {
402 0 : ecoValid_.count++;
403 0 : ecoValid_.results[result]++;
404 0 : eco = eco_Reduce(eco);
405 0 : ecoStats_[eco].count++;
406 0 : ecoStats_[eco].results[result]++;
407 0 : eco /= 27;
408 0 : ecoGroup3_[eco].count++;
409 0 : ecoGroup3_[eco].results[result]++;
410 0 : eco /= 10;
411 0 : ecoGroup2_[eco].count++;
412 0 : ecoGroup2_[eco].results[result]++;
413 0 : eco /= 10;
414 0 : ecoGroup1_[eco].count++;
415 0 : ecoGroup1_[eco].results[result]++;
416 : }
417 : }
418 0 : }
419 :
420 0 : const scidBaseT::Stats::Eco* scidBaseT::Stats::getEcoStats(const char* ecoStr) const {
421 0 : ASSERT(ecoStr != 0);
422 :
423 0 : if (*ecoStr == 0) return &ecoValid_;
424 :
425 0 : ecoT eco = eco_FromString(ecoStr);
426 0 : if (eco == 0) return 0;
427 0 : eco = eco_Reduce(eco);
428 :
429 0 : switch(strlen(ecoStr)) {
430 0 : case 0:
431 0 : return &ecoValid_;
432 0 : case 1:
433 0 : return &(ecoGroup1_[eco / 2700]);
434 0 : case 2:
435 0 : return &(ecoGroup2_[eco / 270]);
436 0 : case 3:
437 0 : return &(ecoGroup3_[eco / 27]);
438 0 : case 4:
439 : case 5:
440 0 : return &(ecoStats_[eco]);
441 : }
442 :
443 0 : return 0;
444 : }
445 :
446 :
447 :
448 : double scidBaseT::TreeStat::expVect_[1600];
449 :
450 0 : scidBaseT::TreeStat::TreeStat()
451 0 : : toMove(NOCOLOR), resultW(0), resultD(0), resultB(0), exp(0), ngames(0), nexp(0)
452 : {
453 0 : if (TreeStat::expVect_[0] == 0) {
454 0 : for (int i=-800; i < 800; i++) TreeStat::expVect_[i+800] = 1/(1 + pow(10, i/400.0));
455 : }
456 0 : }
457 :
458 0 : std::vector<scidBaseT::TreeStat> scidBaseT::getTreeStat(const HFilter& filter) {
459 0 : ASSERT(filter != 0);
460 :
461 0 : std::vector<scidBaseT::TreeStat> res;
462 0 : std::vector<FullMove> v;
463 0 : auto nb = getNameBase();
464 0 : for (gamenumT gnum = 0, n = numGames(); gnum < n; gnum++) {
465 0 : uint ply = filter.get(gnum);
466 0 : if (ply == 0) continue;
467 0 : else ply--;
468 :
469 0 : const IndexEntry* ie = getIndexEntry(gnum);
470 0 : FullMove move = StoredLine::getMove(ie->GetStoredLineCode(), ply);
471 0 : if (!move)
472 0 : move = getGame(ie).getMove(ply);
473 :
474 0 : size_t i = 0;
475 0 : while (i < v.size() && v[i] != move) i++;
476 0 : if (i == v.size()) {
477 0 : v.push_back(move);
478 0 : res.push_back(scidBaseT::TreeStat());
479 : }
480 0 : res[i].add(ie->GetResult(), ie->GetWhiteElo(nb), ie->GetBlackElo(nb));
481 : }
482 :
483 0 : for (size_t i = 0, n = v.size(); i < n; i++) {
484 0 : res[i].SAN = !v[i] ? "[end]" : v[i].getSAN(&(res[i].toMove));
485 : }
486 :
487 0 : std::sort(res.begin(), res.end());
488 0 : return res;
489 : }
490 :
491 0 : errorT scidBaseT::getCompactStat(unsigned long long* n_deleted,
492 : unsigned long long* n_unused,
493 : unsigned long long* n_sparse,
494 : unsigned long long* n_badNameId) {
495 0 : std::vector<uint> nbFreq[NUM_NAME_TYPES];
496 0 : for (nameT n = NAME_PLAYER; n < NUM_NAME_TYPES; n++) {
497 0 : nbFreq[n].resize(getNameBase()->GetNumNames(n), 0);
498 : }
499 :
500 0 : uint64_t last_offset = 0;
501 0 : *n_sparse = 0;
502 0 : *n_deleted = 0;
503 0 : for (gamenumT i=0, n = numGames(); i < n; i++) {
504 0 : const IndexEntry* ie = getIndexEntry (i);
505 0 : if (ie->GetDeleteFlag()) { *n_deleted += 1; continue; }
506 :
507 0 : auto offset = ie->GetOffset();
508 0 : if (offset < last_offset) *n_sparse += 1;
509 0 : last_offset = offset;
510 :
511 0 : nbFreq[NAME_PLAYER][ie->GetWhite()] += 1;
512 0 : nbFreq[NAME_PLAYER][ie->GetBlack()] += 1;
513 0 : nbFreq[NAME_EVENT][ie->GetEvent()] += 1;
514 0 : nbFreq[NAME_SITE][ie->GetSite()] += 1;
515 0 : nbFreq[NAME_ROUND][ie->GetRound()] += 1;
516 : }
517 :
518 0 : *n_unused = 0;
519 0 : for (nameT n = NAME_PLAYER; n < NUM_NAME_TYPES; n++) {
520 0 : *n_unused += std::count(nbFreq[n].begin(), nbFreq[n].end(), 0);
521 : }
522 :
523 0 : *n_badNameId = idx->GetBadNameIdCount();
524 0 : return OK;
525 : }
526 :
527 0 : errorT scidBaseT::compact(const Progress& progress) {
528 0 : std::vector<std::string> filenames = codec_->getFilenames();
529 0 : if (filenames.empty()) return ERROR_CodecUnsupFeat;
530 :
531 0 : if (fileMode_ != FMODE_Both) {
532 : //Older scid version to be upgraded are opened read only
533 0 : if (idx->GetVersion() == SCID_VERSION) return ERROR_FileMode;
534 : }
535 :
536 : //1) Create a new temporary database
537 0 : std::string filename = fileName_;
538 0 : std::string tmpfile = filename + "__COMPACT__";
539 0 : ICodecDatabase::Codec dbtype = codec_->getType();
540 0 : scidBaseT tmp;
541 0 : errorT err_Create = tmp.Open(dbtype, FMODE_Create, tmpfile.c_str());
542 0 : if (err_Create != OK) return err_Create;
543 :
544 : //2) Copy the Index Header
545 0 : tmp.beginTransaction();
546 0 : tmp.idx->copyHeaderInfo(*idx);
547 0 : gamenumT autoloadOld = idx->GetAutoLoad();
548 0 : gamenumT autoloadNew = 1;
549 :
550 : //3) Create the list of games to be copied
551 0 : std::vector< std::pair<uint64_t, gamenumT> > sort;
552 0 : uint n_deleted = 0;
553 0 : for (gamenumT i = 0, n = numGames(); i < n; i++) {
554 0 : const IndexEntry* ie = getIndexEntry(i);
555 0 : if (ie->GetDeleteFlag()) {
556 0 : n_deleted++;
557 0 : continue;
558 : }
559 0 : uint64_t order = static_cast<uint64_t>(ie->GetStoredLineCode()) << 56;
560 0 : const byte* hp = ie->GetHomePawnData();
561 0 : order |= static_cast<uint64_t>(hp[0]) << 48;
562 0 : order |= static_cast<uint64_t>(hp[1]) << 40;
563 0 : order |= static_cast<uint64_t>(hp[2]) << 32;
564 0 : order |= static_cast<uint64_t>(hp[3]) << 24;
565 0 : order |= ie->GetFinalMatSig() & 0xFFFFFF;
566 0 : sort.emplace_back(order, i);
567 : }
568 0 : std::stable_sort(sort.begin(), sort.end());
569 :
570 : //4) Copy the games
571 0 : uint iProgress = 0;
572 0 : bool err_UserCancel = false;
573 0 : errorT err_AddGame = OK;
574 0 : for (auto it = sort.cbegin(); it != sort.cend(); ++it) {
575 0 : err_AddGame = tmp.importGameHelper(this, it->second);
576 0 : if (err_AddGame != OK) break;
577 :
578 0 : gamenumT oldGnum = it->second + 1;
579 0 : if (oldGnum == autoloadOld) autoloadNew = tmp.numGames();
580 : //TODO:
581 : //- update bookmarks game number
582 : // (*it).second == old game number
583 : // tmp.numGames() == new game number
584 0 : if (++iProgress % 8192 == 0) {
585 0 : if (!progress.report(iProgress, sort.size())) {
586 0 : err_UserCancel = true;
587 0 : break;
588 : }
589 : }
590 : }
591 :
592 : //5) Finalize the new database
593 0 : tmp.idx->SetAutoLoad(autoloadNew);
594 0 : std::vector<std::string> tmp_filenames = tmp.codec_->getFilenames();
595 0 : errorT err_NbWrite = tmp.endTransaction();
596 0 : errorT err_Close = tmp.Close();
597 0 : if (err_Close == OK) err_Close = (filenames.size() == tmp_filenames.size()) ? OK : ERROR;
598 :
599 : //6) Error: cleanup and report
600 0 : if (err_NbWrite != OK || err_Close != OK || err_UserCancel || err_AddGame != OK) {
601 0 : for (size_t i = 0, n = tmp_filenames.size(); i < n; i++) {
602 0 : std::remove(tmp_filenames[i].c_str());
603 : }
604 0 : if (err_AddGame != OK)
605 0 : return err_AddGame;
606 0 : if (err_UserCancel)
607 0 : return ERROR_UserCancel;
608 0 : if (err_NbWrite != OK)
609 0 : return err_NbWrite;
610 0 : ASSERT(err_Close != OK);
611 0 : return err_Close;
612 : }
613 :
614 : //7) Remember the active filters and SortCaches
615 0 : std::vector<std::string> filters(filters_.size());
616 0 : for (size_t i = 0, n = filters_.size(); i < n; i++) {
617 0 : filters[i] = filters_[i].first;
618 : }
619 0 : std::vector< std::pair<std::string, int> > oldSC;
620 0 : for (auto& sortCache : sortCaches_) {
621 0 : int refCount = sortCache.second->incrRef(0);
622 0 : if (refCount >= 0)
623 0 : oldSC.emplace_back(sortCache.first, refCount);
624 : }
625 :
626 : //8) Remove the old database
627 0 : if (Close() != OK) return ERROR_FileInUse;
628 0 : for (size_t i = 0, n = filenames.size(); i < n; i++) {
629 0 : if (std::remove(filenames[i].c_str()) != 0) return ERROR_CompactRemove;
630 : }
631 :
632 : //9) Success: rename the files and open the new database
633 0 : for (size_t i = 0, n = filenames.size(); i < n; i++) {
634 0 : const char* s1 = tmp_filenames[i].c_str();
635 0 : const char* s2 = filenames[i].c_str();
636 0 : std::rename(s1, s2);
637 : }
638 0 : errorT res = Open(dbtype, FMODE_Both, filename.c_str());
639 :
640 : //10) Re-create filters and SortCaches
641 0 : if (res == OK || res == ERROR_NameDataLoss) {
642 0 : for (size_t i = 0, n = filters.size(); i < n; i++) {
643 0 : filters_.push_back(
644 0 : std::make_pair(filters[i], new Filter(numGames())));
645 : }
646 0 : for (size_t i = 0, n = oldSC.size(); i < n; i++) {
647 0 : const std::string& criteria = oldSC[i].first;
648 0 : SortCache* sc = SortCache::create(idx, nb_, criteria.c_str());
649 0 : if (sc != NULL) {
650 0 : sc->incrRef(oldSC[i].second);
651 0 : sortCaches_.emplace_back(criteria, sc);
652 : }
653 : }
654 : }
655 :
656 0 : return res;
657 : }
658 :
659 : /**
660 : * Retrieve a SortCache object matching the supplied @e criteria.
661 : * A new SortCache with refCount equal to 0 is created if a suitable object is
662 : * not found in @e sortCaches_. Objects with refCount <= 0 are destroyed by the
663 : * @e releaseSortCache function independently from the provided @e criteria
664 : * argument (implementing a rudimentary garbage collector).
665 : * @param criteria: the list of fields by which games will be ordered.
666 : * Each field should be followed by '+' to indicate an
667 : * ascending order or by '-' for a descending order.
668 : * @returns a pointer to a SortCache object in case of success, NULL otherwise.
669 : */
670 19817 : SortCache* scidBaseT::getSortCache(const char* criteria) {
671 19817 : ASSERT(criteria != NULL);
672 :
673 691102 : for (auto& sortCache : sortCaches_) {
674 675969 : if (sortCache.first == criteria)
675 4684 : return sortCache.second;
676 : }
677 :
678 15133 : SortCache* sc = SortCache::create(idx, getNameBase(), criteria);
679 15133 : if (sc != NULL)
680 76 : sortCaches_.emplace_back(criteria, sc);
681 :
682 15133 : return sc;
683 : }
684 :
685 795 : void scidBaseT::releaseSortCache(const char* criteria) {
686 795 : size_t i = 0;
687 29753 : while (i < sortCaches_.size()) {
688 14479 : const char* tmp = sortCaches_[i].first.c_str();
689 14479 : int decr = std::strcmp(criteria, tmp) ? 0 : -1;
690 14553 : if (sortCaches_[i].second->incrRef(decr) <= 0) {
691 74 : delete sortCaches_[i].second;
692 74 : sortCaches_.erase(sortCaches_.begin() + i);
693 74 : continue; //do not increment i
694 : }
695 14405 : i += 1;
696 : }
697 795 : }
698 :
699 797 : SortCache* scidBaseT::createSortCache(const char* criteria) {
700 797 : SortCache* sc = getSortCache(criteria);
701 797 : if (sc != NULL)
702 80 : sc->incrRef(1);
703 :
704 797 : return sc;
705 : }
706 :
707 15900 : size_t scidBaseT::listGames(const char* criteria, size_t start, size_t count,
708 : const HFilter& filter, gamenumT* destCont) {
709 15900 : const SortCache* sc = getSortCache(criteria);
710 15900 : if (sc == NULL)
711 14340 : return 0;
712 :
713 1560 : return sc->select(start, count, filter, destCont);
714 : }
715 :
716 3120 : size_t scidBaseT::sortedPosition(const char* criteria, const HFilter& filter,
717 : gamenumT gameId) {
718 3120 : ASSERT(filter != NULL && filter->size() <= numGames());
719 :
720 3120 : if (gameId >= numGames() || filter->get(gameId) == 0)
721 0 : return INVALID_GAMEID;
722 :
723 3120 : SortCache* sc = getSortCache(criteria);
724 3120 : if (sc == NULL)
725 0 : return INVALID_GAMEID;
726 :
727 3120 : return sc->sortedPosition(gameId, filter);
728 : }
|