Conversation
Point users to pre-built .deb packages available from the GitHub Releases page, as an alternative to the PPA and building from source. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The local URL probe relies on Future.timeout() to give up after a few seconds, but the underlying io.HttpClient has no connectionTimeout set. On some platforms the OS-level TCP connect retries SYN packets for 30+ seconds to an unreachable local IP, making the app appear to hang. Set connectionTimeout on the HttpClient so the socket layer itself gives up quickly. Also reduce the default timeout from 2 seconds to 500 ms, since anything reachable on the local network responds well within that. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
When a directory is created on the filesystem outside of paperman (or since the cache was last built), refreshCache() calls findItemW() which returns nullptr for the unknown path. The subsequent freeChildren() call on the null pointer causes a segfault. Handle this by dropping the stale cache and rebuilding it from scratch when the directory is not found, matching the existing behaviour for when no cache exists at all. Add a test that reproduces the crash by creating a directory on the filesystem bypassing paperman, then attempting to create a subdirectory inside it. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add testFindFoldersSuggestsMonth() which verifies that findFolders() correctly suggests the next month directory after creating directories through the app within the same session. The test builds the cache, creates 01jan and 02feb via newDir(), then checks that findFolders() suggests 03mar. This exercises the cache update path to ensure the in-memory cache reflects newly created directories. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The uninit handler deletes _label but does not null the pointer, unlike _progress which is correctly set to zero on the next line. If two Operation objects are created and destroyed in sequence, the second uninit attempts to delete the dangling _label pointer, causing a crash. Add the missing null assignment. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
doNewDir() calls _model->mkdir() which internally calls QDirModel::refresh(). This refresh invalidates the QPersistentModelIndex _context in Dirview. Any subsequent operation that depends on _context, such as getRootIndex() used by findFolders() in the pscan dialog, silently returns an invalid index and produces empty results. Save the parent directory path before mkdir and re-establish _context afterwards via selectContextItem(). This matches the approach already used in the test-friendly newDir(path, index) variant, which calls selectDir() after mkdir. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add two tests that exercise the Desktopwidget::findFolders() code path, which is used by the pscan dialog's F4 folder-search feature. testFindFoldersSuggestsMonthViaDesktop simulates the real UI workflow: set _context to bills/2026 as if the user right-clicked, then create directories via doNewDir(), and verify that findFolders() still suggests the next month. Before the previous fix, this test fails because doNewDir() invalidates _context and getRootIndex() returns an invalid index. testFindFoldersSuggestsMonthAfterRefresh does the same but also calls refreshDirmodelCache() between creating directories and searching, to cover the case where the user manually refreshes the cache. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The code stores a pointer from toLatin1().constData() in a local variable, but the temporary QByteArray is destroyed at the end of the statement, leaving a dangling pointer. The subsequent fopen() and utilSetGroup() calls read freed memory. Keep the QByteArray alive in a named variable and use constData() from it instead. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The compressed tile decoder in decode_compressed_tile() reads a 4-byte word from the input buffer at each step. When the input pointer is near the end of the buffer, this read overflows the allocation. Add 4 bytes of zero padding when allocating the cache buffer so that the 4-byte lookahead near the end of compressed tile data does not cause a heap-buffer-overflow. The extra bytes are zero-filled by Qt's resize() and only read data comes from fread() with the original size. Found by AddressSanitizer. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
When insert_chunk() reuses an existing chunk slot that is larger than the new chunk data, it expands new_chunk.size to match the old slot size. However, it does not reallocate new_chunk.buf, leaving the buffer at its original (smaller) size. When flush_chunks() later writes chunk.size bytes from chunk.buf, it reads past the end of the buffer. Reallocate the buffer to match the expanded size and zero-fill the new portion. Found by AddressSanitizer. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
When build_chunk() is called with force=true and chunk.buf already exists, it recalculates chunk.size and allocates a new buffer via alloc_chunk_buf() without freeing the old one. This leaks the previous buffer each time a chunk is force-rebuilt. Free the old buffer before allocating the new one for all three chunk types (bermuda, annot, env). The existing TODO comments indicated the developer was aware of this issue. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Add an icon and prepare for a release on the Play store. Signed-off-by: Simon Glass <sjg@chromium.org>
The colour_image_for_blank() function, which tints colour previews to indicate blank pages, has two bugs that cause colour document previews to display as blank white rectangles: The rgb pointer is never incremented in the inner loop, so only the first pixel of each row is ever processed. Also, the colour calculation adds the component values together as a raw integer instead of constructing a proper QRgb value with qRgb(). For a white pixel this produces an integer ~701 which, interpreted as QRgb, has alpha=0 making the pixel fully transparent. Add the missing rgb++ increment and wrap the calculation in qRgb() to match the pattern used by the greyscale palette entries elsewhere. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Colour (24bpp) preview thumbnails appear blank in the main view because the alpha byte is written as 0 (fully transparent) instead of 0xff (fully opaque). The rle_decode() function expands 3-byte RGB preview pixels to 4-byte RGBX, but sets the fourth byte to zero. When this data is wrapped in a QImage with Format_RGB32 and converted to QPixmap, the zero alpha causes every pixel to be transparent, producing blank white thumbnails. Monochrome previews are unaffected because they use Format_Indexed8 with palette entries from qRgb() which includes alpha=0xff. Change the alpha byte from 0 to 0xff so colour previews render as opaque. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The add_preview() function stores raw 8bpp preview bytes with a plain memcpy(), but decode_8bpp_preview() expects a nibble-based run-length format. This mismatch causes rebuilt greyscale previews to appear completely black. Add encode_8bpp_preview() which quantises each pixel to a 4-bit nibble and emits runs in the format that the decoder expects. The decoder inverts via "255 - value" to bridge the polarity difference between image convention (0=black) and preview colour table (0=white), so the encoder does not invert. Use it in add_preview() for the 8bpp case. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The rebuildPreviews() function processes all pages in a batch, which is not suitable for on-the-fly rebuilding of individual pages. Extract the per-page logic into a new rebuildPagePreview() method that handles a single page: it opens the file, loads chunks, decodes the full image, builds a preview, and flushes. It returns early if the page is not 8bpp greyscale or already has a real preview. Simplify rebuildPreviews() to call it in a loop. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Some old PaperPort .max files have stub previews (4 bytes or fewer) for 8bpp greyscale pages. Rather than requiring a batch rebuild of the whole database, detect these stubs at display time and rebuild automatically. When getPreviewPixmap() encounters a stub preview on an 8bpp page, it frees any temporary chunk, calls rebuildPagePreview() to decode the full image and generate a proper preview, then re-fetches the chunk and continues with normal preview decoding. The rebuilt preview is flushed to disk so this only happens once per page. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Add two tests for the greyscale preview encoding: testPreview8bppRoundtrip() creates a synthetic gradient, encodes it, decodes it, and verifies each pixel is within the nibble quantisation tolerance. It also checks that the decoder correctly inverts the polarity (image 0=black becomes preview 255=black). testPreviewFromJpeg() loads greyscale_gradient.jpg, scales it to preview size, runs the encode/decode roundtrip, and checks the qCompress'd result against a known size, catching any regression in the encoding logic. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The Filejpeg destructor is empty, so the Filejpegpage objects in the _pages list are never freed. The remove() method does clean them up, but that also deletes the files from disk and is not called on normal destruction. Add qDeleteAll() to the destructor to free the page objects. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The Desktopview constructor allocates a Measure object but the destructor is empty, so it is leaked. Add delete to the destructor to free it. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The constructor allocates _map with new but the destructor does not free it, causing a memory leak. Add delete to the destructor. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The constructor creates _model (Dirmodel) and _dir_proxy (Dirproxy) without a parent, so they are not auto-freed by Qt's object hierarchy. The destructor does not free them either, causing a large leak of the directory model and all its internal state. Add delete calls for both, after the view is freed. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
setupJpeg() allocates _buffer with new[] but finishJpeg() frees it with delete instead of delete[]. This is undefined behaviour and is caught by AddressSanitizer. Use delete[] to match the new[] allocation. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
After each scan completes, slotStackConfirm() calls refreshDirmodelCache() which rescans the entire directory tree under the scan path with utilScanDir(). This is visible as a progress bar sweeping across the bottom of the window and causes unnecessary delay, particularly for large directories. Add a targeted addFileToCache() method that inserts just the new filename into the existing in-memory cache tree and writes it out, avoiding the full directory rescan. The full rescan remains available via the "Refresh cache" context-menu action. Also add an optional fname parameter to confirmScan() so the caller can capture the scanned filename before it is cleared. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
The Android app is not on the Play Store, so change the link to download the APK directly from the GitHub release. Also add a link to the iPhone app on the App Store. Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix several memory-safety issues found by AddressSanitizer: heap overflows
in the .max file decoder, use-after-free in Filemax::create(), dangling
pointers, and array delete mismatches. Also fix memory leaks in destructors
for Desktopwidget, Dirmodel, Desktopview, and Filejpeg.
Fix colour .max preview rendering — both the blank-page tinting and the
RLE-decoded thumbnails were producing transparent or black output due to
missing alpha bytes and incorrect pixel iteration. Add greyscale preview
encoding so 8bpp previews round-trip correctly, and auto-rebuild stub
previews on the fly.
Fix stale directory context after creating folders, which broke the
folder-suggestion feature in the scan dialog. Speed up post-scan cache
updates by inserting a single file instead of rescanning the whole tree.
Other changes: app icon for Play Store release, connection-timeout fix in
the Flutter app, site download links for Android/iPhone, and build/release
improvements (do-build fixes, parallelisation,
make releasetarget).