Skip to content

Commit

Permalink
[Experimental] Asynchronous folder read
Browse files Browse the repository at this point in the history
* Add cherry-picked _mod_ of [mez0ru's PR](sylikc#172) for [Issue: Slow startup when opening a file in a highly populated folder](sylikc#194). thanks mez0ru
  * Not fully tested
  • Loading branch information
sdneon committed Jun 11, 2023
1 parent e28729a commit 9dcd89f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 17 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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+<Left/Right arrow>**: Jump back/forward 100 images.
* **CTRL+ALT+<Left/Right arrow>**: Jump to previous/next folder.
Expand All @@ -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).

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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?
Expand Down
54 changes: 43 additions & 11 deletions src/JPEGView/FileList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -347,7 +373,7 @@ void CFileList::FileHasRenamed(LPCTSTR sOldFileName, LPCTSTR sNewFileName) {
m_sInitialFile = sNewFileName;
}
std::list<CFileDesc>::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);
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -541,7 +571,7 @@ LPCTSTR CFileList::CurrentDirectory() const {
int CFileList::CurrentIndex() const {
int i = 0;
std::list<CFileDesc>::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;
}
Expand Down Expand Up @@ -696,7 +726,7 @@ std::list<CFileDesc>::iterator CFileList::FindFile(const CString& sName) {
return m_fileList.begin();
}
std::list<CFileDesc>::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;
}
Expand Down Expand Up @@ -792,7 +822,7 @@ CFileList* CFileList::WrapToNextImage() {
std::list<CString>::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) {
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -1014,7 +1046,7 @@ void CFileList::FindFiles() {

void CFileList::VerifyFiles() {
std::list<CFileDesc>::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()) {
Expand Down
9 changes: 8 additions & 1 deletion src/JPEGView/FileList.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "Helpers.h"
#include <future>

class CDirectoryWatcher;

Expand Down Expand Up @@ -173,6 +174,8 @@ class CFileList
int m_nMarkedIndexShow;

CDirectoryWatcher & m_directoryWatcher;
std::future<void> m_taskFindFiles;
bool m_bFindingFiles, m_bForceExitFindFiles;

void MoveIterToLast();
void NextInFolder();
Expand All @@ -187,8 +190,12 @@ class CFileList
bool HasSubDir();
void GetDirListRcursive(CString sPath, std::list<CString> &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();
}
};

0 comments on commit 9dcd89f

Please sign in to comment.