Skip to content

Commit e01bd3a

Browse files
AUTOMATIC1111ruchej
authored andcommitted
mass file lister as an attempt to tackle AUTOMATIC1111#14507
1 parent 0dde3d4 commit e01bd3a

File tree

3 files changed

+88
-13
lines changed

3 files changed

+88
-13
lines changed

modules/extra_networks.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def parse_prompts(prompts):
206206
return res, extra_data
207207

208208

209-
def get_user_metadata(filename):
209+
def get_user_metadata(filename, lister=None):
210210
if filename is None:
211211
return {}
212212

@@ -215,11 +215,10 @@ def get_user_metadata(filename):
215215

216216
metadata = {}
217217
try:
218-
with open(metadata_filename, "r", encoding="utf8") as file:
219-
metadata = json.load(file)
220-
except FileNotFoundError:
221-
pass
222-
except Exception as e:
223-
errors.display(e, f"reading extra network user metadata from {metadata_filename}")
218+
exists = lister.exists(metadata_filename) if lister else os.path.exists(metadata_filename)
219+
if exists:
220+
with open(metadata_filename, "r", encoding="utf8") as file:
221+
metadata = json.load(file)
222+
except Exception as e: errors.display(e, f"reading extra network user metadata from {metadata_filename}")
224223

225224
return metadata

modules/ui_extra_networks.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import urllib.parse
44
from pathlib import Path
55

6-
from modules import shared, ui_extra_networks_user_metadata, errors, extra_networks
6+
from modules import shared, ui_extra_networks_user_metadata, errors, extra_networks, util
77
from modules.images import read_info_from_image, save_image_with_geninfo
88
import gradio as gr
99
import json
@@ -107,6 +107,7 @@ def __init__(self, title):
107107
self.allow_negative_prompt = False
108108
self.metadata = {}
109109
self.items = {}
110+
self.lister = util.MassFileLister()
110111

111112
def refresh(self):
112113
pass
@@ -123,7 +124,7 @@ def read_user_metadata(self, item):
123124

124125
def link_preview(self, filename):
125126
quoted_filename = urllib.parse.quote(filename.replace('\\', '/'))
126-
mtime = os.path.getmtime(filename)
127+
mtime, _ = self.lister.mctime(filename)
127128
return f"./sd_extra_networks/thumb?filename={quoted_filename}&mtime={mtime}"
128129

129130
def search_terms_from_path(self, filename, possible_directories=None):
@@ -137,6 +138,8 @@ def search_terms_from_path(self, filename, possible_directories=None):
137138
return ""
138139

139140
def create_html(self, tabname):
141+
self.lister.reset()
142+
140143
items_html = ''
141144

142145
self.metadata = {}
@@ -282,10 +285,10 @@ def get_sort_keys(self, path):
282285
List of default keys used for sorting in the UI.
283286
"""
284287
pth = Path(path)
285-
stat = pth.stat()
288+
mtime, ctime = self.lister.mctime(path)
286289
return {
287-
"date_created": int(stat.st_ctime or 0),
288-
"date_modified": int(stat.st_mtime or 0),
290+
"date_created": int(mtime),
291+
"date_modified": int(ctime),
289292
"name": pth.name.lower(),
290293
"path": str(pth.parent).lower(),
291294
}
@@ -298,7 +301,7 @@ def find_preview(self, path):
298301
potential_files = sum([[path + "." + ext, path + ".preview." + ext] for ext in allowed_preview_extensions()], [])
299302

300303
for file in potential_files:
301-
if os.path.isfile(file):
304+
if self.lister.exists(file):
302305
return self.link_preview(file)
303306

304307
return None
@@ -308,6 +311,9 @@ def find_description(self, path):
308311
Find and read a description file for a given path (without extension).
309312
"""
310313
for file in [f"{path}.txt", f"{path}.description.txt"]:
314+
if not self.lister.exists(file):
315+
continue
316+
311317
try:
312318
with open(file, "r", encoding="utf-8", errors="replace") as f:
313319
return f.read()

modules/util.py

+70
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,73 @@ def truncate_path(target_path, base_path=cwd):
6666
except ValueError:
6767
pass
6868
return abs_target
69+
70+
71+
class MassFileListerCachedDir:
72+
"""A class that caches file metadata for a specific directory."""
73+
74+
def __init__(self, dirname):
75+
self.files = None
76+
self.files_cased = None
77+
self.dirname = dirname
78+
79+
stats = ((x.name, x.stat(follow_symlinks=False)) for x in os.scandir(self.dirname))
80+
files = [(n, s.st_mtime, s.st_ctime) for n, s in stats]
81+
self.files = {x[0].lower(): x for x in files}
82+
self.files_cased = {x[0]: x for x in files}
83+
84+
85+
class MassFileLister:
86+
"""A class that provides a way to check for the existence and mtime/ctile of files without doing more than one stat call per file."""
87+
88+
def __init__(self):
89+
self.cached_dirs = {}
90+
91+
def find(self, path):
92+
"""
93+
Find the metadata for a file at the given path.
94+
95+
Returns:
96+
tuple or None: A tuple of (name, mtime, ctime) if the file exists, or None if it does not.
97+
"""
98+
99+
dirname, filename = os.path.split(path)
100+
101+
cached_dir = self.cached_dirs.get(dirname)
102+
if cached_dir is None:
103+
cached_dir = MassFileListerCachedDir(dirname)
104+
self.cached_dirs[dirname] = cached_dir
105+
106+
stats = cached_dir.files_cased.get(filename)
107+
if stats is not None:
108+
return stats
109+
110+
stats = cached_dir.files.get(filename.lower())
111+
if stats is None:
112+
return None
113+
114+
try:
115+
os_stats = os.stat(path, follow_symlinks=False)
116+
return filename, os_stats.st_mtime, os_stats.st_ctime
117+
except Exception:
118+
return None
119+
120+
def exists(self, path):
121+
"""Check if a file exists at the given path."""
122+
123+
return self.find(path) is not None
124+
125+
def mctime(self, path):
126+
"""
127+
Get the modification and creation times for a file at the given path.
128+
129+
Returns:
130+
tuple: A tuple of (mtime, ctime) if the file exists, or (0, 0) if it does not.
131+
"""
132+
133+
stats = self.find(path)
134+
return (0, 0) if stats is None else stats[1:3]
135+
136+
def reset(self):
137+
"""Clear the cache of all directories."""
138+
self.cached_dirs.clear()

0 commit comments

Comments
 (0)