From 9dcd89fb0929792136623adf6cc4a49d7709a61a Mon Sep 17 00:00:00 2001 From: David Neon Date: Sun, 11 Jun 2023 22:55:42 +0800 Subject: [PATCH] [Experimental] Asynchronous folder read * Add cherry-picked _mod_ of [mez0ru's PR](https://github.com/sylikc/jpegview/pull/172) for [Issue: Slow startup when opening a file in a highly populated folder](https://github.com/sylikc/jpegview/issues/194). thanks mez0ru * Not fully tested --- README.md | 27 ++++++++++++++++---- src/JPEGView/FileList.cpp | 54 +++++++++++++++++++++++++++++++-------- src/JPEGView/FileList.h | 9 ++++++- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2128bd6a..bc92486d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ JPEGView is a lean, fast and highly configurable image viewer/editor with a mini JPEGView has built-in support the following formats: * Popular: JPEG, GIF -* Lossless: BMP, PNG, TIFF, QOI, ICO +* Lossless: BMP, PNG, TIFF, QOI, ICO (mod only) * Web: WEBP, JXL, HEIF/HEIC, AVIF (common subset) * Legacy: TGA, WDP, HDP, JXR * Camera RAW formats: @@ -47,7 +47,7 @@ Basic on-the-fly image processing is provided - allowing adjusting typical param * Default to panning mode. Dedicated 'Selection mode' can be toggled via remapped 'S' hotkey. * Quick zoom to selection mode via remapped hotkey 'Z'. * Option for selection box to match image aspect ratio. -* Toggle transparent image background between checkerboard pattern (default) and solid background colour, via hotkey: SHIFT+V. +* Toggle 3 transparency modes: TransparencyColor (default), checkerboard pattern or inverse TransparencyColor, via hotkey: SHIFT+V. * Navigation * **ALT+**: Jump back/forward 100 images. * **CTRL+ALT+**: Jump to previous/next folder. @@ -60,10 +60,14 @@ Basic on-the-fly image processing is provided - allowing adjusting typical param * Old format still supported. * Use ConvertKeyMap tool to make one-off conversion if desired. * Others + * Release includes all necessary DLLs. * Toast notifications. * Toggle ascending/descending sorting by pressing the same hotkey for sorting mode. - * Added `Auto` folder navigation mode to auto-choose `LoopSubFolders` (if initial folder has subfolder) or `LoopSameFolderLevel` (otherwise). - * Command# 6000 (LOOP_FOLDER, hotkey: F7) now toggles between `LoopFolder` and `Auto`. + * New settings and/or options: + * Added `Auto` folder navigation mode to auto-choose `LoopSubFolders` (if initial folder has subfolder) or `LoopSameFolderLevel` (otherwise). + * Command# 6000 (LOOP_FOLDER, hotkey: F7) now toggles between `LoopFolder` and `Auto`. + * Set `MinFilesize > 0` to Hide of small images. It auto-disables temporarily if 1st image opened is small (< MinFilesize), so as to view that image as intended. + * [Experimental]: include a mod of [mez0ru's PR for quick image show despite large folder](https://github.com/sylikc/jpegview/pull/172) (Last selectively sync'd up to original's ~31 Jan 2023 updates, with occasional cherry picks going ahead). @@ -94,7 +98,8 @@ JPEGView has a slideshow mode which can be activated in various ways: * E.g.: `JPEGView.exe /slideshow 2` (**modified in mod**) starts JPEGView in image selection mode, and then starts slideshow with image switching at 2s intervals. (Previously when an image/path is not specified, `/slideshow` is ignored) * Slideshow no longer paused when jumping into an image from a different folder. -* `FolderNavigation` setting defaults to `Auto`. +* New `Auto` option for `FolderNavigation` setting. +* During slideshow, block screensaver. * A little Android-like `toast` to inform of new slideshow fps or interval. Also used for other general notifications of interest. * PS: there's an existing toast-like display of zoom factor when zooming - just in a smaller font. @@ -178,6 +183,18 @@ Configure in `JPEGView.ini`: * Specify in bytes, KB or MB like so: 30720, 30K or 1M * Enable hide hidden images and folders: `HideHidden`, default: true. +### Asynchronous FileList + +mez0ru has a [PR](https://github.com/sylikc/jpegview/pull/172) for [Issue: Slow startup when opening a file in a highly populated folder](https://github.com/sylikc/jpegview/issues/194). + +I've tried it out a _mod_ of it on a folder with 25k small images. Differences: +* If app is launched with an image specified (instead of a folder), it will show that image (by adding it to the file list) first. Asynchronous loading of files in that image's folder, is started thereafter. This ensures that initial image is quickly displayed. +* Limit code changes to FileList.cpp/h files only. + +From timing printouts, it does help 'significantly'. +However 'by feel', perhaps owing to my defragmented drive, opening an image in a folder with 25k is still _very fast_ (< 1s)! Thus, I can't really test other situations like jumping 100 images, etc. As such, I'll leave this patch as is. + + ### Wishlist * Filter images by date, like show newest images only? diff --git a/src/JPEGView/FileList.cpp b/src/JPEGView/FileList.cpp index 7d634496..82b198fa 100644 --- a/src/JPEGView/FileList.cpp +++ b/src/JPEGView/FileList.cpp @@ -197,7 +197,9 @@ CFileList::CFileList(const CString & sInitialFile, CDirectoryWatcher & directory int nMinFilesize, bool bHideHidden) : m_directoryWatcher(directoryWatcher), m_nMinFilesize(nMinFilesize), - m_bHideHidden(bHideHidden) + m_bHideHidden(bHideHidden), + m_bFindingFiles(false), + m_bForceExitFindFiles(false) { CFileDesc::SetSorting(eInitialSorting, isSortedUpcounting); @@ -226,9 +228,28 @@ CFileList::CFileList(const CString & sInitialFile, CDirectoryWatcher & directory if (!m_bIsSlideShowList) { if (bImageFile || bIsDirectory) { - FindFiles(); - m_iter = FindFile(sInitialFile); - m_iterStart = m_fileList.begin(); + m_iterStart = m_iter = m_fileList.begin(); + if (bImageFile) + { //at least show 1st selected image 1st + CFindFile fileFind; + if (fileFind.FindFile(sInitialFile)) { + AddToFileList(m_fileList, fileFind, sExtensionInitialFile, m_nMinFilesize, m_bHideHidden); + m_iterStart = m_iter = m_fileList.begin(); + } + } + //move potentially heavy search to separate thread + m_bFindingFiles = true; + m_taskFindFiles = std::async(std::launch::async, [this, &sInitialFile, &bWrapAroundFolder]() { + FindFiles(false); + if (m_bForceExitFindFiles) { + m_bForceExitFindFiles = false; + return; + } + m_iter = FindFile(sInitialFile); + m_iterStart = m_fileList.begin(); + m_bFindingFiles = false; + m_bForceExitFindFiles = false; + }); } else { // neither image file nor directory nor list of file names - try to read anyway but normally will fail CFindFile fileFind; @@ -265,6 +286,11 @@ CString CFileList::GetSupportedFileEndings() { void CFileList::Reload(LPCTSTR sFileName, bool clearForwardHistory) { LPCTSTR sCurrent = sFileName; if (sCurrent == NULL) { + // If async is still processing, quickly terminate it. + m_bForceExitFindFiles = true; + WaitIfNotReady(); + m_bForceExitFindFiles = false; + sCurrent = Current(); if (sCurrent == NULL) { m_fileList.clear(); @@ -347,7 +373,7 @@ void CFileList::FileHasRenamed(LPCTSTR sOldFileName, LPCTSTR sNewFileName) { m_sInitialFile = sNewFileName; } std::list::iterator iter; - for (iter = m_fileList.begin( ); iter != m_fileList.end( ); iter++ ) { + for (iter = m_fileList.begin(); iter != m_fileList.end(); iter++ ) { if (_tcsicmp(sOldFileName, iter->GetName()) == 0) { iter->SetName(sNewFileName); } @@ -497,6 +523,8 @@ void CFileList::Last() { } CFileList* CFileList::AwayFromCurrent() { + WaitIfNotReady(); // If async is still processing, then wait. + LPCTSTR sCurrentFile = Current(); LPCTSTR sNextFile = PeekNextPrev(1, true, false); if (sCurrentFile == NULL || sNextFile == NULL || _tcscmp(sCurrentFile, sNextFile) == 0) { @@ -512,6 +540,8 @@ CFileList* CFileList::AwayFromCurrent() { } LPCTSTR CFileList::Current() const { + if ((m_fileList.size() == 0) && m_bFindingFiles) + m_taskFindFiles.wait(); if (m_iter != m_fileList.end()) { return m_iter->GetName(); } else { @@ -541,7 +571,7 @@ LPCTSTR CFileList::CurrentDirectory() const { int CFileList::CurrentIndex() const { int i = 0; std::list::const_iterator iter; - for (iter = m_fileList.begin( ); iter != m_fileList.end( ); iter++ ) { + for (iter = m_fileList.begin(); iter != m_fileList.end(); iter++) { if (iter == m_iter) { return i; } @@ -696,7 +726,7 @@ std::list::iterator CFileList::FindFile(const CString& sName) { return m_fileList.begin(); } std::list::iterator iter; - for (iter = m_fileList.begin( ); iter != m_fileList.end( ); iter++ ) { + for (iter = m_fileList.begin(); iter != m_fileList.end(); iter++ ) { if (_tcsicmp((LPCTSTR)sName + nStart, iter->GetTitle()) == 0) { return iter; } @@ -792,7 +822,7 @@ CFileList* CFileList::WrapToNextImage() { std::list::iterator iter; bool bFound = false; for (int nStep = 0; nStep < 2; nStep++) { - for (iter = dirList.begin( ); iter != dirList.end( ); iter++ ) { + for (iter = dirList.begin(); iter != dirList.end(); iter++ ) { if (iter->CompareNoCase(sThisDirTitle) == 0) { bFound = true; } else if (bFound) { @@ -994,15 +1024,17 @@ CFileList* CFileList::TryCreateFileList(const CString& directory, int nNewLevel, } } -void CFileList::FindFiles() { - m_fileList.clear(); +void CFileList::FindFiles(bool bPurge1st) { + if (bPurge1st) m_fileList.clear(); if (!m_sDirectory.IsEmpty()) { CFindFile fileFind; LPCTSTR* allFileEndings = GetSupportedFileEndingList(); for (int i = 0; i < nNumEndings; i++) { + if (m_bForceExitFindFiles) return; if (fileFind.FindFile(m_sDirectory + _T("\\*.") + allFileEndings[i])) { AddToFileList(m_fileList, fileFind, allFileEndings[i], m_nMinFilesize, m_bHideHidden); while (fileFind.FindNextFile()) { + if (m_bForceExitFindFiles) return; AddToFileList(m_fileList, fileFind, allFileEndings[i], m_nMinFilesize, m_bHideHidden); } } @@ -1014,7 +1046,7 @@ void CFileList::FindFiles() { void CFileList::VerifyFiles() { std::list::iterator iter; - for (iter = m_fileList.begin( ); iter != m_fileList.end( ); iter++ ) { + for (iter = m_fileList.begin(); iter != m_fileList.end(); iter++ ) { if (::GetFileAttributes(iter->GetName()) == INVALID_FILE_ATTRIBUTES) { iter = m_fileList.erase(iter); if (iter == m_fileList.end()) { diff --git a/src/JPEGView/FileList.h b/src/JPEGView/FileList.h index 5b6a36ea..00a6fee2 100644 --- a/src/JPEGView/FileList.h +++ b/src/JPEGView/FileList.h @@ -1,6 +1,7 @@ #pragma once #include "Helpers.h" +#include class CDirectoryWatcher; @@ -173,6 +174,8 @@ class CFileList int m_nMarkedIndexShow; CDirectoryWatcher & m_directoryWatcher; + std::future m_taskFindFiles; + bool m_bFindingFiles, m_bForceExitFindFiles; void MoveIterToLast(); void NextInFolder(); @@ -187,8 +190,12 @@ class CFileList bool HasSubDir(); void GetDirListRcursive(CString sPath, std::list &dirList, CString &sThisDirTitle); CFileList* AnyAvailableLast(); - void FindFiles(); + void FindFiles(bool bPurge1st = true); void VerifyFiles(); bool IsImageFile(const CString & sEnding); bool TryReadingSlideShowList(const CString & sSlideShowFile); + inline void CFileList::WaitIfNotReady() { + if (m_bFindingFiles) + m_taskFindFiles.wait(); + } };