A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: Removed redundant FsFile close() calls (#1434)

## Summary

**What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

`DESTRUCTOR_CLOSES_FILE=1` is set in platformio.ini, which makes SdFat's
FsBaseFile destructor call close() automatically when a file goes out of
scope.

Three categories of file close calls remain untouched:
1. Close before Storage.remove() on the same path: ScreenshotUtil.cpp
closes the file before deleting it on write error. The remove might fail
if the file is still open.
2. Close before reopening the same variable: Epub.cpp writes a temp
NCX/nav file, closes it, then reopens it for reading. The
RecentBooksStore.cpp close before saveToFile() is the same pattern, it
rewrites the same file.
3. Close on member variables: BookMetadataCache.cpp (bookFile,
spineFile, tocFile), Section.cpp (file), XtcParser.cpp (m_file),
ZipFile.cpp (file). These persist beyond any single function scope, so
the destructor timing doesn't match the intended close point.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_

authored by

Zach Nelson and committed by
GitHub
23aad213 075ad7d0

+46 -104
+15 -9
lib/Epub/Epub.cpp
··· 155 155 return false; 156 156 } 157 157 readItemContentsToStream(tocNcxItem, tempNcxFile, 1024); 158 + // Explicitly close() file before reopening for reading 158 159 tempNcxFile.close(); 159 160 if (!Storage.openFileForRead("EBP", tmpNcxPath, tempNcxFile)) { 160 161 return false; ··· 165 166 166 167 if (!ncxParser.setup()) { 167 168 LOG_ERR("EBP", "Could not setup toc ncx parser"); 168 - tempNcxFile.close(); 169 169 return false; 170 170 } 171 171 172 172 const auto ncxBuffer = static_cast<uint8_t*>(malloc(1024)); 173 173 if (!ncxBuffer) { 174 174 LOG_ERR("EBP", "Could not allocate memory for toc ncx parser"); 175 - tempNcxFile.close(); 176 175 return false; 177 176 } 178 177 ··· 184 183 if (processedSize != readSize) { 185 184 LOG_ERR("EBP", "Could not process all toc ncx data"); 186 185 free(ncxBuffer); 187 - tempNcxFile.close(); 188 186 return false; 189 187 } 190 188 } 191 189 192 190 free(ncxBuffer); 191 + // Explicitly close() file before calling Storage.remove() 193 192 tempNcxFile.close(); 194 193 Storage.remove(tmpNcxPath.c_str()); 195 194 ··· 212 211 return false; 213 212 } 214 213 readItemContentsToStream(tocNavItem, tempNavFile, 1024); 214 + // Explicitly close() file before reopening for reading 215 215 tempNavFile.close(); 216 216 if (!Storage.openFileForRead("EBP", tmpNavPath, tempNavFile)) { 217 217 return false; ··· 241 241 if (processedSize != readSize) { 242 242 LOG_ERR("EBP", "Could not process all toc nav data"); 243 243 free(navBuffer); 244 - tempNavFile.close(); 245 244 return false; 246 245 } 247 246 } 248 247 249 248 free(navBuffer); 249 + // Explicitly close() file before calling Storage.remove() 250 250 tempNavFile.close(); 251 251 Storage.remove(tmpNavPath.c_str()); 252 252 ··· 304 304 } 305 305 if (!readItemContentsToStream(cssPath, tempCssFile, 1024)) { 306 306 LOG_ERR("EBP", "Could not read CSS file: %s", cssPath.c_str()); 307 + // Explicitly close() file before calling Storage.remove() 307 308 tempCssFile.close(); 308 309 Storage.remove(tmpCssPath.c_str()); 309 310 continue; 310 311 } 312 + // Explicitly close() file before reopening for reading 311 313 tempCssFile.close(); 312 314 313 315 // Parse the CSS file ··· 317 319 continue; 318 320 } 319 321 cssParser->loadFromStream(tempCssFile); 322 + // Explicitly close() file before calling Storage.remove() 320 323 tempCssFile.close(); 321 324 Storage.remove(tmpCssPath.c_str()); 322 325 } ··· 547 550 return false; 548 551 } 549 552 readItemContentsToStream(coverImageHref, coverJpg, 1024); 553 + // Explicitly close() file before reopening for reading 550 554 coverJpg.close(); 551 555 552 556 if (!Storage.openFileForRead("EBP", coverJpgTempPath, coverJpg)) { ··· 555 559 556 560 FsFile coverBmp; 557 561 if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) { 558 - coverJpg.close(); 559 562 return false; 560 563 } 561 564 const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp, cropped); 565 + // Explicitly close() files before calling Storage.remove() 562 566 coverJpg.close(); 563 567 coverBmp.close(); 564 568 Storage.remove(coverJpgTempPath.c_str()); ··· 580 584 return false; 581 585 } 582 586 readItemContentsToStream(coverImageHref, coverPng, 1024); 587 + // Explicitly close() file before reopening for reading 583 588 coverPng.close(); 584 589 585 590 if (!Storage.openFileForRead("EBP", coverPngTempPath, coverPng)) { ··· 588 593 589 594 FsFile coverBmp; 590 595 if (!Storage.openFileForWrite("EBP", getCoverBmpPath(cropped), coverBmp)) { 591 - coverPng.close(); 592 596 return false; 593 597 } 594 598 const bool success = PngToBmpConverter::pngFileToBmpStream(coverPng, coverBmp, cropped); 599 + // Explicitly close() files before calling Storage.remove() 595 600 coverPng.close(); 596 601 coverBmp.close(); 597 602 Storage.remove(coverPngTempPath.c_str()); ··· 634 639 return false; 635 640 } 636 641 readItemContentsToStream(coverImageHref, coverJpg, 1024); 642 + // Explicitly close() file before reopening for reading 637 643 coverJpg.close(); 638 644 639 645 if (!Storage.openFileForRead("EBP", coverJpgTempPath, coverJpg)) { ··· 642 648 643 649 FsFile thumbBmp; 644 650 if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) { 645 - coverJpg.close(); 646 651 return false; 647 652 } 648 653 // Use smaller target size for Continue Reading card (half of screen: 240x400) ··· 651 656 int THUMB_TARGET_HEIGHT = height; 652 657 const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH, 653 658 THUMB_TARGET_HEIGHT); 659 + // Explicitly close() files before calling Storage.remove() 654 660 coverJpg.close(); 655 661 thumbBmp.close(); 656 662 Storage.remove(coverJpgTempPath.c_str()); ··· 670 676 return false; 671 677 } 672 678 readItemContentsToStream(coverImageHref, coverPng, 1024); 679 + // Explicitly close() file before reopening for reading 673 680 coverPng.close(); 674 681 675 682 if (!Storage.openFileForRead("EBP", coverPngTempPath, coverPng)) { ··· 678 685 679 686 FsFile thumbBmp; 680 687 if (!Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp)) { 681 - coverPng.close(); 682 688 return false; 683 689 } 684 690 int THUMB_TARGET_WIDTH = height * 0.6; 685 691 int THUMB_TARGET_HEIGHT = height; 686 692 const bool success = 687 693 PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH, THUMB_TARGET_HEIGHT); 694 + // Explicitly close() files before calling Storage.remove() 688 695 coverPng.close(); 689 696 thumbBmp.close(); 690 697 Storage.remove(coverPngTempPath.c_str()); ··· 702 709 // Write an empty bmp file to avoid generation attempts in the future 703 710 FsFile thumbBmp; 704 711 Storage.openFileForWrite("EBP", getThumbBmpPath(height), thumbBmp); 705 - thumbBmp.close(); 706 712 return false; 707 713 } 708 714
+8
lib/Epub/Epub/BookMetadataCache.cpp
··· 33 33 } 34 34 35 35 bool BookMetadataCache::endContentOpfPass() { 36 + // Explicit close() required: member variable persists beyond function scope 36 37 spineFile.close(); 37 38 return true; 38 39 } ··· 44 45 return false; 45 46 } 46 47 if (!Storage.openFileForWrite("BMC", cachePath + tmpTocBinFile, tocFile)) { 48 + // Explicit close() required: member variable persists beyond function scope 47 49 spineFile.close(); 48 50 return false; 49 51 } ··· 75 77 } 76 78 77 79 bool BookMetadataCache::endTocPass() { 80 + // Explicit close() required: member variables persist beyond function scope 78 81 tocFile.close(); 79 82 spineFile.close(); 80 83 ··· 103 106 } 104 107 105 108 if (!Storage.openFileForRead("BMC", cachePath + tmpSpineBinFile, spineFile)) { 109 + // Explicit close() required: member variable persists beyond function scope 106 110 bookFile.close(); 107 111 return false; 108 112 } 109 113 110 114 if (!Storage.openFileForRead("BMC", cachePath + tmpTocBinFile, tocFile)) { 115 + // Explicit close() required: member variables persist beyond function scope 111 116 bookFile.close(); 112 117 spineFile.close(); 113 118 return false; ··· 168 173 // Pre-open zip file to speed up size calculations 169 174 if (!zip.open()) { 170 175 LOG_ERR("BMC", "Could not open EPUB zip for size calculations"); 176 + // Explicit close() required: member variables persist beyond function scope 171 177 bookFile.close(); 172 178 spineFile.close(); 173 179 tocFile.close(); ··· 265 271 writeTocEntry(bookFile, tocEntry); 266 272 } 267 273 274 + // Explicit close() required: member variables persist beyond function scope 268 275 bookFile.close(); 269 276 spineFile.close(); 270 277 tocFile.close(); ··· 373 380 serialization::readPod(bookFile, version); 374 381 if (version != BOOK_CACHE_VERSION) { 375 382 LOG_DBG("BMC", "Cache version mismatch: expected %d, got %d", BOOK_CACHE_VERSION, version); 383 + // Explicit close() required: member variable persists beyond function scope 376 384 bookFile.close(); 377 385 return false; 378 386 }
+8 -3
lib/Epub/Epub/Section.cpp
··· 74 74 uint8_t version; 75 75 serialization::readPod(file, version); 76 76 if (version != SECTION_FILE_VERSION) { 77 + // Explicit close() required: member variable persists beyond function scope 77 78 file.close(); 78 79 LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version); 79 80 clearCache(); ··· 103 104 viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight || 104 105 hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle || 105 106 imageRendering != fileImageRendering) { 107 + // Explicit close() required: member variable persists beyond function scope 106 108 file.close(); 107 109 LOG_ERR("SCT", "Deserialization failed: Parameters do not match"); 108 110 clearCache(); ··· 111 113 } 112 114 113 115 serialization::readPod(file, pageCount); 116 + // Explicit close() required: member variable persists beyond function scope 114 117 file.close(); 115 118 LOG_DBG("SCT", "Deserialization succeeded: %d pages", pageCount); 116 119 return true; ··· 165 168 } 166 169 success = epub->readItemContentsToStream(localPath, tmpHtml, 1024); 167 170 fileSize = tmpHtml.size(); 171 + // Explicitly close() file before calling Storage.remove() 168 172 tmpHtml.close(); 169 173 170 174 // If streaming failed, remove the incomplete file immediately ··· 214 218 Storage.remove(tmpHtmlPath.c_str()); 215 219 if (!success) { 216 220 LOG_ERR("SCT", "Failed to parse XML and build pages"); 221 + // Explicitly close() file before calling Storage.remove() 217 222 file.close(); 218 223 Storage.remove(filePath.c_str()); 219 224 if (cssParser) { ··· 235 240 236 241 if (hasFailedLutRecords) { 237 242 LOG_ERR("SCT", "Failed to write LUT due to invalid page positions"); 243 + // Explicitly close() file before calling Storage.remove() 238 244 file.close(); 239 245 Storage.remove(filePath.c_str()); 240 246 return false; ··· 254 260 serialization::writePod(file, pageCount); 255 261 serialization::writePod(file, lutOffset); 256 262 serialization::writePod(file, anchorMapOffset); 263 + // Explicit close() required: member variable persists beyond function scope 257 264 file.close(); 258 265 if (cssParser) { 259 266 cssParser->clear(); ··· 275 282 file.seek(pagePos); 276 283 277 284 auto page = Page::deserialize(file); 285 + // Explicit close() required: member variable persists beyond function scope 278 286 file.close(); 279 287 return page; 280 288 } ··· 290 298 uint32_t anchorMapOffset; 291 299 serialization::readPod(f, anchorMapOffset); 292 300 if (anchorMapOffset == 0 || anchorMapOffset >= fileSize) { 293 - f.close(); 294 301 return std::nullopt; 295 302 } 296 303 ··· 303 310 serialization::readString(f, key); 304 311 serialization::readPod(f, page); 305 312 if (key == anchor) { 306 - f.close(); 307 313 return page; 308 314 } 309 315 } 310 316 311 - f.close(); 312 317 return std::nullopt; 313 318 }
-5
lib/Epub/Epub/blocks/ImageBlock.cpp
··· 37 37 38 38 uint16_t cachedWidth, cachedHeight; 39 39 if (cacheFile.read(&cachedWidth, 2) != 2 || cacheFile.read(&cachedHeight, 2) != 2) { 40 - cacheFile.close(); 41 40 return false; 42 41 } 43 42 ··· 47 46 if (widthDiff > 1 || heightDiff > 1) { 48 47 LOG_ERR("IMG", "Cache dimension mismatch: %dx%d vs %dx%d", cachedWidth, cachedHeight, expectedWidth, 49 48 expectedHeight); 50 - cacheFile.close(); 51 49 return false; 52 50 } 53 51 ··· 62 60 uint8_t* rowBuffer = (uint8_t*)malloc(bytesPerRow); 63 61 if (!rowBuffer) { 64 62 LOG_ERR("IMG", "Failed to allocate row buffer"); 65 - cacheFile.close(); 66 63 return false; 67 64 } 68 65 ··· 73 70 if (cacheFile.read(rowBuffer, bytesPerRow) != bytesPerRow) { 74 71 LOG_ERR("IMG", "Cache read error at row %d", row); 75 72 free(rowBuffer); 76 - cacheFile.close(); 77 73 return false; 78 74 } 79 75 ··· 89 85 } 90 86 91 87 free(rowBuffer); 92 - cacheFile.close(); 93 88 LOG_DBG("IMG", "Cache render complete"); 94 89 return true; 95 90 }
+1 -16
lib/Epub/Epub/css/CssParser.cpp
··· 743 743 } 744 744 745 745 LOG_DBG("CSS", "Saved %u rules to cache", ruleCount); 746 - file.close(); 747 746 return true; 748 747 } 749 748 ··· 765 764 if (file.read(&version, 1) != 1 || version != CssParser::CSS_CACHE_VERSION) { 766 765 LOG_DBG("CSS", "Cache version mismatch (got %u, expected %u), removing stale cache for rebuild", version, 767 766 CssParser::CSS_CACHE_VERSION); 767 + // Explicitly close() file before calling Storage.remove() 768 768 file.close(); 769 769 Storage.remove((cachePath + rulesCache).c_str()); 770 770 return false; ··· 773 773 // Read rule count 774 774 uint16_t ruleCount = 0; 775 775 if (file.read(&ruleCount, sizeof(ruleCount)) != sizeof(ruleCount)) { 776 - file.close(); 777 776 return false; 778 777 } 779 778 780 779 if (ruleCount > MAX_RULES) { 781 780 LOG_DBG("CSS", "Invalid cache rule count (%u > %zu)", ruleCount, MAX_RULES); 782 781 rulesBySelector_.clear(); 783 - file.close(); 784 782 return false; 785 783 } 786 784 ··· 799 797 uint16_t selectorLen = 0; 800 798 if (!hasRemainingBytes(sizeof(selectorLen))) { 801 799 rulesBySelector_.clear(); 802 - file.close(); 803 800 return false; 804 801 } 805 802 if (file.read(&selectorLen, sizeof(selectorLen)) != sizeof(selectorLen)) { 806 803 rulesBySelector_.clear(); 807 - file.close(); 808 804 return false; 809 805 } 810 806 811 807 if (selectorLen == 0 || selectorLen > MAX_SELECTOR_LENGTH || !hasRemainingBytes(selectorLen)) { 812 808 LOG_DBG("CSS", "Invalid selector length in cache: %u", selectorLen); 813 809 rulesBySelector_.clear(); 814 - file.close(); 815 810 return false; 816 811 } 817 812 ··· 819 814 selector.resize(selectorLen); 820 815 if (file.read(&selector[0], selectorLen) != selectorLen) { 821 816 rulesBySelector_.clear(); 822 - file.close(); 823 817 return false; 824 818 } 825 819 826 820 if (!hasRemainingBytes(CSS_FIXED_STYLE_BYTES)) { 827 821 LOG_DBG("CSS", "Truncated CSS cache while reading style payload"); 828 822 rulesBySelector_.clear(); 829 - file.close(); 830 823 return false; 831 824 } 832 825 ··· 836 829 837 830 if (file.read(&enumVal, 1) != 1) { 838 831 rulesBySelector_.clear(); 839 - file.close(); 840 832 return false; 841 833 } 842 834 style.textAlign = static_cast<CssTextAlign>(enumVal); 843 835 844 836 if (file.read(&enumVal, 1) != 1) { 845 837 rulesBySelector_.clear(); 846 - file.close(); 847 838 return false; 848 839 } 849 840 style.fontStyle = static_cast<CssFontStyle>(enumVal); 850 841 851 842 if (file.read(&enumVal, 1) != 1) { 852 843 rulesBySelector_.clear(); 853 - file.close(); 854 844 return false; 855 845 } 856 846 style.fontWeight = static_cast<CssFontWeight>(enumVal); 857 847 858 848 if (file.read(&enumVal, 1) != 1) { 859 849 rulesBySelector_.clear(); 860 - file.close(); 861 850 return false; 862 851 } 863 852 style.textDecoration = static_cast<CssTextDecoration>(enumVal); ··· 880 869 !readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight) || 881 870 !readLength(style.imageHeight) || !readLength(style.imageWidth)) { 882 871 rulesBySelector_.clear(); 883 - file.close(); 884 872 return false; 885 873 } 886 874 ··· 888 876 uint8_t displayVal; 889 877 if (file.read(&displayVal, 1) != 1) { 890 878 rulesBySelector_.clear(); 891 - file.close(); 892 879 return false; 893 880 } 894 881 style.display = static_cast<CssDisplay>(displayVal); ··· 897 884 uint16_t definedBits = 0; 898 885 if (file.read(&definedBits, sizeof(definedBits)) != sizeof(definedBits)) { 899 886 rulesBySelector_.clear(); 900 - file.close(); 901 887 return false; 902 888 } 903 889 style.defined.textAlign = (definedBits & 1 << 0) != 0; ··· 921 907 } 922 908 923 909 LOG_DBG("CSS", "Loaded %u rules from cache", ruleCount); 924 - file.close(); 925 910 return true; 926 911 }
-4
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
··· 1018 1018 XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks 1019 1019 XML_SetCharacterDataHandler(parser, nullptr); 1020 1020 XML_ParserFree(parser); 1021 - file.close(); 1022 1021 return false; 1023 1022 } 1024 1023 ··· 1030 1029 XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks 1031 1030 XML_SetCharacterDataHandler(parser, nullptr); 1032 1031 XML_ParserFree(parser); 1033 - file.close(); 1034 1032 return false; 1035 1033 } 1036 1034 ··· 1043 1041 XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks 1044 1042 XML_SetCharacterDataHandler(parser, nullptr); 1045 1043 XML_ParserFree(parser); 1046 - file.close(); 1047 1044 return false; 1048 1045 } 1049 1046 } while (!done); ··· 1053 1050 XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks 1054 1051 XML_SetCharacterDataHandler(parser, nullptr); 1055 1052 XML_ParserFree(parser); 1056 - file.close(); 1057 1053 1058 1054 // Process last page if there is still text 1059 1055 if (currentTextBlock) {
-3
lib/I18n/I18n.cpp
··· 71 71 serialization::readPod(file, version); 72 72 if (version != SETTINGS_VERSION) { 73 73 Serial.printf("[I18N] Settings version mismatch\n"); 74 - file.close(); 75 74 return; 76 75 } 77 76 ··· 81 80 _language = static_cast<Language>(lang); 82 81 Serial.printf("[I18N] Loaded language: %d\n", static_cast<int>(_language)); 83 82 } 84 - 85 - file.close(); 86 83 } 87 84 88 85 // Generate character set for a specific language
-2
lib/KOReaderSync/KOReaderCredentialStore.cpp
··· 82 82 serialization::readPod(file, version); 83 83 if (version != KOREADER_FILE_VERSION) { 84 84 LOG_DBG("KRS", "Unknown file version: %u", version); 85 - file.close(); 86 85 return false; 87 86 } 88 87 ··· 113 112 matchMethod = DocumentMatchMethod::FILENAME; 114 113 } 115 114 116 - file.close(); 117 115 LOG_DBG("KRS", "Loaded KOReader credentials from binary for user: %s", username.c_str()); 118 116 return true; 119 117 }
-2
lib/KOReaderSync/KOReaderDocumentId.cpp
··· 84 84 } 85 85 } 86 86 87 - file.close(); 88 - 89 87 // Calculate final hash 90 88 md5.calculate(); 91 89 std::string result = md5.toString().c_str();
-9
lib/Txt/Txt.cpp
··· 120 120 return false; 121 121 } 122 122 if (!Storage.openFileForWrite("TXT", getCoverBmpPath(), dst)) { 123 - src.close(); 124 123 return false; 125 124 } 126 125 uint8_t buffer[1024]; ··· 128 127 size_t bytesRead = src.read(buffer, sizeof(buffer)); 129 128 dst.write(buffer, bytesRead); 130 129 } 131 - src.close(); 132 - dst.close(); 133 130 LOG_DBG("TXT", "Copied BMP cover to cache"); 134 131 return true; 135 132 } else if (FsHelpers::hasJpgExtension(coverImagePath)) { ··· 140 137 return false; 141 138 } 142 139 if (!Storage.openFileForWrite("TXT", getCoverBmpPath(), coverBmp)) { 143 - coverJpg.close(); 144 140 return false; 145 141 } 146 142 const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp); 147 - coverJpg.close(); 148 - coverBmp.close(); 149 143 150 144 if (!success) { 151 145 LOG_ERR("TXT", "Failed to generate BMP from JPG cover image"); ··· 172 166 } 173 167 174 168 if (!file.seek(offset)) { 175 - file.close(); 176 169 return false; 177 170 } 178 171 179 172 size_t bytesRead = file.read(buffer, length); 180 - file.close(); 181 - 182 173 return bytesRead > 0; 183 174 }
-6
lib/Xtc/Xtc.cpp
··· 199 199 uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(dstRowSize)); 200 200 if (!rowBuffer) { 201 201 free(pageBuffer); 202 - coverBmp.close(); 203 202 return false; 204 203 } 205 204 ··· 255 254 } 256 255 } 257 256 258 - coverBmp.close(); 259 257 free(pageBuffer); 260 258 261 259 LOG_DBG("XTC", "Generated cover BMP: %s", getCoverBmpPath().c_str()); ··· 316 314 size_t bytesRead = src.read(buffer, sizeof(buffer)); 317 315 dst.write(buffer, bytesRead); 318 316 } 319 - dst.close(); 320 317 } 321 - src.close(); 322 318 } 323 319 LOG_DBG("XTC", "Copied cover to thumb (no scaling needed)"); 324 320 return Storage.exists(getThumbBmpPath(height).c_str()); ··· 372 368 uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(rowSize)); 373 369 if (!rowBuffer) { 374 370 free(pageBuffer); 375 - thumbBmp.close(); 376 371 return false; 377 372 } 378 373 ··· 479 474 } 480 475 481 476 free(rowBuffer); 482 - thumbBmp.close(); 483 477 free(pageBuffer); 484 478 485 479 LOG_DBG("XTC", "Generated thumb BMP (%dx%d): %s", thumbWidth, thumbHeight, getThumbBmpPath(height).c_str());
+6
lib/Xtc/Xtc/XtcParser.cpp
··· 43 43 m_lastError = readHeader(); 44 44 if (m_lastError != XtcError::OK) { 45 45 LOG_DBG("XTC", "Failed to read header: %s", errorToString(m_lastError)); 46 + // Explicit close() required: member variable persists beyond function scope 46 47 m_file.close(); 47 48 return m_lastError; 48 49 } ··· 52 53 m_lastError = readTitle(); 53 54 if (m_lastError != XtcError::OK) { 54 55 LOG_DBG("XTC", "Failed to read title: %s", errorToString(m_lastError)); 56 + // Explicit close() required: member variable persists beyond function scope 55 57 m_file.close(); 56 58 return m_lastError; 57 59 } 58 60 m_lastError = readAuthor(); 59 61 if (m_lastError != XtcError::OK) { 60 62 LOG_DBG("XTC", "Failed to read author: %s", errorToString(m_lastError)); 63 + // Explicit close() required: member variable persists beyond function scope 61 64 m_file.close(); 62 65 return m_lastError; 63 66 } ··· 67 70 m_lastError = readPageTable(); 68 71 if (m_lastError != XtcError::OK) { 69 72 LOG_DBG("XTC", "Failed to read page table: %s", errorToString(m_lastError)); 73 + // Explicit close() required: member variable persists beyond function scope 70 74 m_file.close(); 71 75 return m_lastError; 72 76 } ··· 75 79 m_lastError = readChapters(); 76 80 if (m_lastError != XtcError::OK) { 77 81 LOG_DBG("XTC", "Failed to read chapters: %s", errorToString(m_lastError)); 82 + // Explicit close() required: member variable persists beyond function scope 78 83 m_file.close(); 79 84 return m_lastError; 80 85 } ··· 86 91 87 92 void XtcParser::close() { 88 93 if (m_isOpen) { 94 + // Explicit close() required: member variable persists beyond function scope 89 95 m_file.close(); 90 96 m_isOpen = false; 91 97 }
+1
lib/ZipFile/ZipFile.cpp
··· 278 278 279 279 bool ZipFile::close() { 280 280 if (file) { 281 + // Explicit close() required: member variable persists beyond function scope 281 282 file.close(); 282 283 } 283 284 lastCentralDirPos = 0;
-2
src/CrossPointSettings.cpp
··· 126 126 serialization::readPod(inputFile, version); 127 127 if (version != SETTINGS_FILE_VERSION) { 128 128 LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version); 129 - inputFile.close(); 130 129 return false; 131 130 } 132 131 ··· 220 219 applyLegacyFrontButtonLayout(*this); 221 220 } 222 221 223 - inputFile.close(); 224 222 LOG_DBG("CPS", "Settings loaded from binary file"); 225 223 return true; 226 224 }
-2
src/CrossPointState.cpp
··· 55 55 serialization::readPod(inputFile, version); 56 56 if (version > STATE_FILE_VERSION) { 57 57 LOG_ERR("CPS", "Deserialization failed: Unknown version %u", version); 58 - inputFile.close(); 59 58 return false; 60 59 } 61 60 ··· 76 75 lastSleepFromReader = false; 77 76 } 78 77 79 - inputFile.close(); 80 78 return true; 81 79 }
+1 -2
src/RecentBooksStore.cpp
··· 163 163 } 164 164 165 165 if (omitted > 0) { 166 + // Explicitly close() file before saveToFile() rewrites the same file 166 167 inputFile.close(); 167 168 saveToFile(); 168 169 LOG_DBG("RBS", "Omitted %u recent book(s) with missing title", omitted); ··· 170 171 } 171 172 } else { 172 173 LOG_ERR("RBS", "Deserialization failed: Unknown version %u", version); 173 - inputFile.close(); 174 174 return false; 175 175 } 176 176 177 - inputFile.close(); 178 177 LOG_DBG("RBS", "Recent books loaded from binary file (%d entries)", static_cast<int>(recentBooks.size())); 179 178 return true; 180 179 }
-2
src/WifiCredentialStore.cpp
··· 76 76 serialization::readPod(file, version); 77 77 if (version > WIFI_FILE_VERSION) { 78 78 LOG_DBG("WCS", "Unknown file version: %u", version); 79 - file.close(); 80 79 return false; 81 80 } 82 81 ··· 98 97 credentials.push_back(cred); 99 98 } 100 99 101 - file.close(); 102 100 // LOG_DBG("WCS", "Loaded %zu WiFi credentials from binary file", credentials.size()); 103 101 return true; 104 102 }
-15
src/activities/boot_sleep/SleepActivity.cpp
··· 43 43 if (dir && dir.isDirectory()) { 44 44 sleepDir = "/.sleep"; 45 45 } else { 46 - if (dir) dir.close(); 47 46 dir = Storage.open("/sleep"); 48 47 if (dir && dir.isDirectory()) { 49 48 sleepDir = "/sleep"; ··· 56 55 // collect all valid BMP files 57 56 for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { 58 57 if (file.isDirectory()) { 59 - file.close(); 60 58 continue; 61 59 } 62 60 file.getName(name, sizeof(name)); 63 61 auto filename = std::string(name); 64 62 if (filename[0] == '.') { 65 - file.close(); 66 63 continue; 67 64 } 68 65 69 66 if (!FsHelpers::hasBmpExtension(filename)) { 70 67 LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name); 71 - file.close(); 72 68 continue; 73 69 } 74 70 Bitmap bitmap(file); 75 71 if (bitmap.parseHeaders() != BmpReaderError::Ok) { 76 72 LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); 77 - file.close(); 78 73 continue; 79 74 } 80 75 files.emplace_back(filename); 81 - file.close(); 82 76 } 83 77 const auto numFiles = files.size(); 84 78 if (numFiles > 0) { ··· 98 92 Bitmap bitmap(file, true); 99 93 if (bitmap.parseHeaders() == BmpReaderError::Ok) { 100 94 renderBitmapSleepScreen(bitmap); 101 - file.close(); 102 - dir.close(); 103 95 return; 104 96 } 105 - file.close(); 106 97 } 107 98 } 108 99 } 109 - if (dir) dir.close(); 110 - 111 100 // Look for sleep.bmp on the root of the sd card to determine if we should 112 101 // render a custom sleep screen instead of the default. 113 102 FsFile file; ··· 116 105 if (bitmap.parseHeaders() == BmpReaderError::Ok) { 117 106 LOG_DBG("SLP", "Loading: /sleep.bmp"); 118 107 renderBitmapSleepScreen(bitmap); 119 - file.close(); 120 108 return; 121 109 } 122 - file.close(); 123 110 } 124 111 125 112 renderDefaultSleepScreen(); ··· 286 273 if (bitmap.parseHeaders() == BmpReaderError::Ok) { 287 274 LOG_DBG("SLP", "Rendering sleep cover: %s", coverBmpPath.c_str()); 288 275 renderBitmapSleepScreen(bitmap); 289 - file.close(); 290 276 return; 291 277 } 292 - file.close(); 293 278 } 294 279 295 280 return (this->*renderNoCoverSleepScreen)();
-6
src/activities/home/FileBrowserActivity.cpp
··· 75 75 76 76 auto root = Storage.open(basepath.c_str()); 77 77 if (!root || !root.isDirectory()) { 78 - if (root) root.close(); 79 78 return; 80 79 } 81 80 ··· 85 84 for (auto file = root.openNextFile(); file; file = root.openNextFile()) { 86 85 file.getName(name, sizeof(name)); 87 86 if ((!SETTINGS.showHiddenFiles && name[0] == '.') || strcmp(name, "System Volume Information") == 0) { 88 - file.close(); 89 87 continue; 90 88 } 91 89 ··· 99 97 files.emplace_back(filename); 100 98 } 101 99 } 102 - file.close(); 103 100 } 104 - root.close(); 105 101 sortFileList(files); 106 102 } 107 103 ··· 115 111 basepath = "/"; 116 112 loadFiles(); 117 113 } else if (!root.isDirectory()) { 118 - root.close(); 119 114 lockLongPressBack = mappedInput.isPressed(MappedInputManager::Button::Back); 120 115 121 116 const std::string oldPath = basepath; ··· 126 121 const std::string fileName = oldPath.substr(pos + 1); 127 122 selectorIndex = findEntry(fileName); 128 123 } else { 129 - root.close(); 130 124 loadFiles(); 131 125 } 132 126
-2
src/activities/reader/EpubReaderActivity.cpp
··· 69 69 if (dataSize == 6) { 70 70 cachedChapterTotalPageCount = data[4] + (data[5] << 8); 71 71 } 72 - f.close(); 73 72 } 74 73 // We may want a better condition to detect if we are opening for the first time. 75 74 // This will trigger if the book is re-opened at Chapter 0. ··· 696 695 data[4] = pageCount & 0xFF; 697 696 data[5] = (pageCount >> 8) & 0xFF; 698 697 f.write(data, 6); 699 - f.close(); 700 698 LOG_DBG("ERS", "Progress saved: Chapter %d, Page %d", spineIndex, currentPage); 701 699 } else { 702 700 LOG_ERR("ERS", "Could not save progress!");
-12
src/activities/reader/TxtReaderActivity.cpp
··· 405 405 data[2] = 0; 406 406 data[3] = 0; 407 407 f.write(data, 4); 408 - f.close(); 409 408 } 410 409 } 411 410 ··· 423 422 } 424 423 LOG_DBG("TRS", "Loaded progress: page %d/%d", currentPage, totalPages); 425 424 } 426 - f.close(); 427 425 } 428 426 } 429 427 ··· 452 450 serialization::readPod(f, magic); 453 451 if (magic != CACHE_MAGIC) { 454 452 LOG_DBG("TRS", "Cache magic mismatch, rebuilding"); 455 - f.close(); 456 453 return false; 457 454 } 458 455 ··· 460 457 serialization::readPod(f, version); 461 458 if (version != CACHE_VERSION) { 462 459 LOG_DBG("TRS", "Cache version mismatch (%d != %d), rebuilding", version, CACHE_VERSION); 463 - f.close(); 464 460 return false; 465 461 } 466 462 ··· 468 464 serialization::readPod(f, fileSize); 469 465 if (fileSize != txt->getFileSize()) { 470 466 LOG_DBG("TRS", "Cache file size mismatch, rebuilding"); 471 - f.close(); 472 467 return false; 473 468 } 474 469 ··· 476 471 serialization::readPod(f, cachedWidth); 477 472 if (cachedWidth != viewportWidth) { 478 473 LOG_DBG("TRS", "Cache viewport width mismatch, rebuilding"); 479 - f.close(); 480 474 return false; 481 475 } 482 476 ··· 484 478 serialization::readPod(f, cachedLines); 485 479 if (cachedLines != linesPerPage) { 486 480 LOG_DBG("TRS", "Cache lines per page mismatch, rebuilding"); 487 - f.close(); 488 481 return false; 489 482 } 490 483 ··· 492 485 serialization::readPod(f, fontId); 493 486 if (fontId != cachedFontId) { 494 487 LOG_DBG("TRS", "Cache font ID mismatch (%d != %d), rebuilding", fontId, cachedFontId); 495 - f.close(); 496 488 return false; 497 489 } 498 490 ··· 500 492 serialization::readPod(f, margin); 501 493 if (margin != cachedScreenMargin) { 502 494 LOG_DBG("TRS", "Cache screen margin mismatch, rebuilding"); 503 - f.close(); 504 495 return false; 505 496 } 506 497 ··· 508 499 serialization::readPod(f, alignment); 509 500 if (alignment != cachedParagraphAlignment) { 510 501 LOG_DBG("TRS", "Cache paragraph alignment mismatch, rebuilding"); 511 - f.close(); 512 502 return false; 513 503 } 514 504 ··· 525 515 pageOffsets.push_back(offset); 526 516 } 527 517 528 - f.close(); 529 518 totalPages = pageOffsets.size(); 530 519 LOG_DBG("TRS", "Loaded page index cache: %d pages", totalPages); 531 520 return true; ··· 555 544 serialization::writePod(f, static_cast<uint32_t>(offset)); 556 545 } 557 546 558 - f.close(); 559 547 LOG_DBG("TRS", "Saved page index cache: %d pages", totalPages); 560 548 }
-2
src/components/themes/BaseTheme.cpp
··· 422 422 bookWidth = rect.width / 2; // Fallback 423 423 } 424 424 } 425 - file.close(); 426 425 } 427 426 } 428 427 ··· 476 475 renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4); 477 476 } 478 477 } 479 - file.close(); 480 478 } 481 479 } 482 480
+3
src/network/CrossPointWebServer.cpp
··· 193 193 } 194 194 195 195 void CrossPointWebServer::abortWsUpload(const char* tag) { 196 + // Explicit close() required: file-scope global persists beyond function scope 196 197 wsUploadFile.close(); 197 198 String filePath = wsUploadPath; 198 199 if (!filePath.endsWith("/")) filePath += "/"; ··· 1342 1343 1343 1344 // Zero-byte upload: complete immediately without waiting for BIN frames 1344 1345 if (wsUploadSize == 0) { 1346 + // Explicit close() required: file-scope global persists beyond function scope 1345 1347 wsUploadFile.close(); 1346 1348 wsLastCompleteName = wsUploadFileName; 1347 1349 wsLastCompleteSize = 0; ··· 1397 1399 1398 1400 // Check if upload complete 1399 1401 if (wsUploadReceived >= wsUploadSize) { 1402 + // Explicit close() required: file-scope global persists beyond function scope 1400 1403 wsUploadFile.close(); 1401 1404 wsUploadInProgress = false; 1402 1405 wsUploadClientNum = 255;
+3
src/util/ScreenshotUtil.cpp
··· 70 70 } 71 71 72 72 if (write_error) { 73 + // Explicitly close() file before calling Storage.remove() 73 74 file.close(); 74 75 Storage.remove(filename); 75 76 return false; ··· 80 81 constexpr size_t kMaxRowSize = 68; 81 82 if (rowSizePadded > kMaxRowSize) { 82 83 LOG_ERR("SCR", "Row size %u exceeds buffer capacity", rowSizePadded); 84 + // Explicitly close() file before calling Storage.remove() 83 85 file.close(); 84 86 Storage.remove(filename); 85 87 return false; ··· 106 108 memset(rowBuffer, 0, rowSizePadded); // Clear the buffer for the next row 107 109 } 108 110 111 + // Explicitly close() file before calling Storage.remove() 109 112 file.close(); 110 113 111 114 if (write_error) {