Skip to content
Merged

Deva #68

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
24b55d8
doc: Add link to the latest GitHub Release
sjg20 Feb 22, 2026
e56c85c
app: Set a connection timeout for the local URL probe
sjg20 Feb 22, 2026
b9b735c
dirmodel: Fix crash creating a dir not in the cache
sjg20 Mar 2, 2026
d96b10a
test: Add test for folder month suggestion after newDir
sjg20 Mar 2, 2026
4cf6529
mainwindow: Null _label after deletion in updateProgress()
sjg20 Mar 2, 2026
d348723
desktopwidget: Fix stale Dirview context after doNewDir()
sjg20 Mar 2, 2026
181f3b1
test: Add tests for folder suggestions via the desktop path
sjg20 Mar 2, 2026
0b43d09
filemax: Fix use-after-free in Filemax::create()
sjg20 Mar 2, 2026
3ff01f6
filemax: Add padding in max_cache_data() for 4-byte lookahead
sjg20 Mar 2, 2026
50bc542
filemax: Fix heap overflow in insert_chunk() when reusing larger slot
sjg20 Mar 2, 2026
ea40427
filemax: Fix memory leaks in build_chunk()
sjg20 Mar 2, 2026
e447253
app: Provide an icon ready for release
sjg20 Mar 2, 2026
e50b837
file: Fix blank colour previews in colour_image_for_blank()
sjg20 Mar 2, 2026
501810d
filemax: Fix transparent colour previews in rle_decode()
sjg20 Mar 2, 2026
70328a7
filemax: Add encode_8bpp_preview() for greyscale preview encoding
sjg20 Mar 2, 2026
f44cad6
filemax: Extract rebuildPagePreview() from rebuildPreviews()
sjg20 Mar 2, 2026
f0338e4
filemax: Rebuild stub previews on the fly in getPreviewPixmap()
sjg20 Mar 2, 2026
1609cdc
test: Add 8bpp preview encode/decode tests
sjg20 Mar 2, 2026
2b3c352
filejpeg: Free page objects in Filejpeg destructor
sjg20 Mar 2, 2026
1e4162b
desktopview: Free Measure object in destructor
sjg20 Mar 2, 2026
2d22a2f
dirmodel: Free map in Dirmodel destructor
sjg20 Mar 2, 2026
80620fe
desktopwidget: Free model and proxy in destructor
sjg20 Mar 2, 2026
0b76e37
paperstack: Fix array delete mismatch in finishJpeg()
sjg20 Mar 3, 2026
b685275
dirmodel: Add a single file to the cache after scan
sjg20 Mar 5, 2026
746c83c
site: Add app download links for Android and iPhone
sjg20 Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ android {
play {
serviceAccountCredentials.set(file("play-account.json"))
track.set("internal")
releaseStatus.set(com.github.triplet.gradle.androidpublisher.ReleaseStatus.DRAFT)
defaultToAppBundles.set(true)
}

Expand Down
Binary file added app/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 9 additions & 3 deletions app/lib/services/api_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,27 @@ class ApiService {
/// When connecting to a local IP the server's certificate is issued
/// for the domain name, not the IP address. This client accepts
/// that mismatch for the specific local host only.
http.Client _createTrustingClient(String host) {
http.Client _createTrustingClient(
String host, {
Duration? connectionTimeout,
}) {
final ioClient = io.HttpClient()
..badCertificateCallback =
(io.X509Certificate cert, String h, int port) => h == host;
if (connectionTimeout != null) {
ioClient.connectionTimeout = connectionTimeout;
}
return IOClient(ioClient);
}

/// Try the local URL first; if it responds within [timeout], use it
/// as the active base URL. Otherwise fall back to the main URL.
Future<void> tryLocalUrl({
Duration timeout = const Duration(seconds: 2),
Duration timeout = const Duration(milliseconds: 500),
}) async {
if (_localBaseUrl == null || _localBaseUrl!.isEmpty) return;
final uri = Uri.parse('$_localBaseUrl/status');
final client = _createTrustingClient(uri.host);
final client = _createTrustingClient(uri.host, connectionTimeout: timeout);
try {
final response = await client.get(uri, headers: _headers)
.timeout(timeout);
Expand Down
2 changes: 1 addition & 1 deletion app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.3.0+1
version: 1.3.0+2

environment:
sdk: ^3.11.0
Expand Down
4 changes: 3 additions & 1 deletion desktopmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,11 +836,13 @@ err_info *Desktopmodel::cancelScan (void)
}


err_info *Desktopmodel::confirmScan (void)
err_info *Desktopmodel::confirmScan (QString *fname)
{
// qDebug () << "Desktopmodel::confirmScan";
Q_ASSERT (_scan_desk && _scan_file);

if (fname)
*fname = _scan_file->filename ();
QModelIndex ind = index (_scan_file->filename (), _scan_parent);

// flush the item
Expand Down
6 changes: 4 additions & 2 deletions desktopmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,10 @@ class Desktopmodel : public QAbstractItemModel
\returns error, or NULL if none */
err_info *beginScan (QModelIndex parent, const QString &stack_name);

/** confirm and save the pending scan */
err_info *confirmScan (void);
/** confirm and save the pending scan
*
* \param fname if non-null, returns the filename of the scanned stack */
err_info *confirmScan (QString *fname = nullptr);

/** cancel and remove the pending scan */
err_info *cancelScan (void);
Expand Down
1 change: 1 addition & 0 deletions desktopview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Desktopview::Desktopview (QWidget *parent)

Desktopview::~Desktopview ()
{
delete _measure;
}

Measure *Desktopview::getMeasure()
Expand Down
24 changes: 22 additions & 2 deletions desktopwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ Desktopwidget::~Desktopwidget ()
delete _modelconv;
delete _modelconv_assert;
delete _dir;
delete _dir_proxy;
delete _model;
}


Expand Down Expand Up @@ -781,12 +783,23 @@ QModelIndex Desktopwidget::doNewDir(const QString& name, QString& path)
{
QModelIndex index = _dir->menuGetModelIndex ();
QModelIndex src_ind = _dir_proxy->mapToSource(index);
path = _model->data(src_ind, QDirModel::FilePathRole).toString() +
QDir::separator() + name;
QString parentPath = _model->data(src_ind,
QDirModel::FilePathRole).toString();
path = parentPath + QDir::separator() + name;

Operation op("Creating directory", 0, this);
QModelIndex new_ind = _model->mkdir(src_ind, name, &op);

// mkdir() refreshes the QDirModel, which can invalidate the
// proxy model's persistent indices, including _context in Dirview.
// Re-select the parent so that getRootIndex() and other operations
// that depend on _context still work.
src_ind = _model->index(parentPath);
if (src_ind.isValid()) {
QModelIndex proxy_ind = _dir_proxy->mapFromSource(src_ind);
_dir->selectContextItem(proxy_ind);
}

return new_ind;
}

Expand Down Expand Up @@ -885,7 +898,14 @@ void Desktopwidget::refreshDirmodelCache(const QString& dirPath)

Operation op("Updating cache", 0, this);
_model->refreshCacheFrom(src_ind, &op);
}

void Desktopwidget::addFileToDirmodelCache(const QString &dirPath,
const QString &filename)
{
QModelIndex src_ind = _model->index(dirPath);

_model->addFileToCache(src_ind, filename);
}

void Desktopwidget::slotDirChanged (QString &dirPath, QModelIndex &deskind)
Expand Down
8 changes: 8 additions & 0 deletions desktopwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ class Desktopwidget : public QSplitter //QWidget
* @param dirPath Full path to the directory to update
*/
void refreshDirmodelCache(const QString& dirPath);

/**
* @brief Add a single file to the dirmodel cache
* @param dirPath Full path to the directory containing the file
* @param filename Leaf filename to add
*/
void addFileToDirmodelCache(const QString &dirPath,
const QString &filename);
protected:
//bool eventFilter (QObject *watched_object, QEvent *e);

Expand Down
46 changes: 46 additions & 0 deletions dirmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ bool Diritem::refreshCache(const QString dirPath, Operation *op)
Q_ASSERT(_dir_cache);
top = _dir_cache->findItemW(rel);

if (!top) {
// Directory is not in the cache, e.g. it was created outside the
// app since the cache was last built. Rebuild from scratch.
dropCache();
if (!buildCache(op))
return false;
return true;
}

TreeItem *updated = utilScanDir(dirPath, op);

top->freeChildren();
Expand All @@ -172,6 +181,31 @@ bool Diritem::refreshCache(const QString dirPath, Operation *op)
return true;
}

bool Diritem::addFileToCache(const QString &dirPath,
const QString &filename)
{
if (!_dir_cache && !readCache())
return false;

QString rel = dirPath.mid(_dir.size() + 1);

Q_ASSERT(_dir_cache);
TreeItem *top = _dir_cache->findItemW(rel);
if (!top)
return false;

QVector<QVariant> columnData = {filename};
TreeItem *child = new TreeItem(columnData, top);

top->appendChild(child);
if (!utilWriteTree(dirCacheFilename(), _dir_cache)) {
qInfo() << "Failed to write cache";
return false;
}

return true;
}

QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
Dirmodel::Dirmodel (QObject * parent)
Expand All @@ -196,6 +230,7 @@ Dirmodel::~Dirmodel ()
{
while (!_item.empty ())
delete _item.takeFirst ();
delete _map;
}

// inline bool indexValid(const QModelIndex &index) const {
Expand Down Expand Up @@ -944,3 +979,14 @@ void Dirmodel::refreshCacheFrom(const QModelIndex& parent, Operation *op)
Q_ASSERT(item);
item->refreshCache(path, op);
}

void Dirmodel::addFileToCache(const QModelIndex &parent,
const QString &filename)
{
QDir dir;
QString path = dir.absoluteFilePath(filePath(parent));

Diritem *item = findItem(parent);
if (item)
item->addFileToCache(path, filename);
}
15 changes: 15 additions & 0 deletions dirmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ class Diritem
*/
bool refreshCache(const QString path, Operation *op);

/**
* @brief Add a single file to the cache
* @param dirPath Full path to the directory containing the file
* @param filename Leaf filename to add
* @return true if OK, false on failure
*/
bool addFileToCache(const QString &dirPath, const QString &filename);

private:
// Get the filename for the dir cache
QString dirCacheFilename() const;
Expand Down Expand Up @@ -292,6 +300,13 @@ class Dirmodel : public QDirModel
*/
void refreshCacheFrom(const QModelIndex& parent, Operation *op);

/**
* @brief Add a single file to the cache
* @param parent Parent index of the directory containing the file
* @param filename Leaf filename to add
*/
void addFileToCache(const QModelIndex &parent, const QString &filename);

private:
/** counts the number of files in 'path', adds it to count and returns it.
Stops if count > max
Expand Down
7 changes: 7 additions & 0 deletions doc/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ This provides pre-built packages for the following Ubuntu releases:
- Noble (24.04 LTS)
- Questing (25.10)

Pre-built .deb Packages
-----------------------

Pre-built ``.deb`` packages for a range of Debian and Ubuntu releases are
available from the `latest GitHub Release
<https://github.com/sjg20/paperman/releases/latest>`_.

Building from Source
--------------------

Expand Down
6 changes: 4 additions & 2 deletions file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,12 @@ void File::colour_image_for_blank (QImage &image)
for (i = 0; i < image.height (); i++)
{
rgb = (QRgb *)image.scanLine (i);
for (int x = 0; x < image.width (); x++)
for (int x = 0; x < image.width (); x++, rgb++)
{
col = *rgb;
*rgb = qRed (col) * CONFIG_preview_col_mult + qGreen (col) * CONFIG_preview_col_mult + qBlue (col);
*rgb = qRgb (qRed (col) * CONFIG_preview_col_mult,
qGreen (col) * CONFIG_preview_col_mult,
qBlue (col));
}
}
}
Expand Down
1 change: 1 addition & 0 deletions filejpeg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Filejpeg::Filejpeg (const QString &dir, const QString &filename, Desk *desk)

Filejpeg::~Filejpeg ()
{
qDeleteAll (_pages);
}

err_info *Filejpeg::load (void)
Expand Down
Loading
Loading