Skip to content

Commit 155f064

Browse files
committed
New: Thumbnail generation speed has been improved
1 parent 7a6b487 commit 155f064

5 files changed

Lines changed: 220 additions & 90 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
- Improve chapter detection in tracking [`ad05a23`](https://github.com/ollm/OpenComic/commit/ad05a2364882227688cccfa2fdf58a21f8e9615c)
1212
- Support for `JP2` image format [`e11046b`](https://github.com/ollm/OpenComic/commit/e11046b4eca758f8b15c1caeb831a235a5f3d62c)
1313
- Support for `JXR` image format [`0e24922`](https://github.com/ollm/OpenComic/commit/0e2492264e1dbc94126c7d79e3e3c50fc426846c)
14-
- Support for `JXL` image format
14+
- Support for `JXL` image format [`bd6fb0a`](https://github.com/ollm/OpenComic/commit/bd6fb0a8c8be3685b48c88fa21bbe2d3c384a491)
15+
- Thumbnail generation speed has been improved
16+
1517

1618
## [v1.4.1](https://github.com/ollm/OpenComic/releases/tag/v1.4.1) (08-02-2025)
1719

‎scripts/cache.js‎

Lines changed: 36 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ catch (error)
1616
zstd = zstdEncoder = zstdDecoder = false;
1717
}
1818

19-
var queuedImages = [], processingTheImageQueue = false;
20-
2119
var cacheFolder = p.join(electronRemote.app.getPath('cache'), 'opencomic');
2220

2321
if(process.platform == 'win32' || process.platform == 'win64')
@@ -27,13 +25,8 @@ if(!fs.existsSync(cacheFolder)) fs.mkdirSync(cacheFolder);
2725
cacheFolder = p.join(cacheFolder, 'cache');
2826
if(!fs.existsSync(cacheFolder)) fs.mkdirSync(cacheFolder);
2927

30-
var imagesWithoutSaving = 0;
31-
32-
async function processTheImageQueue()
28+
async function processTheImageQueue(img = false)
3329
{
34-
let img = queuedImages.shift();
35-
if(!img) return;
36-
3730
let sha = img.sha;
3831

3932
let realPath = fileManager.realPath(img.file);
@@ -51,65 +44,31 @@ async function processTheImageQueue()
5144
if(inArray(fileExtension(realPath), imageExtensions.blob)) // Convert unsupported images to blob
5245
blob = await workers.convertImageToBlob(realPath);
5346

54-
image.resize(blob || realPath, toImage, {blob: blob ? true : false, width: img.size, height: Math.round(img.size * (img.type ? ratios[img.type][forceSize] : 1.5)), quality: 95, fit: img.type ? fit[img.type] : 'inside'}).then(function(){
47+
return new Promise(function(resolve, reject) {
5548

56-
//if(blob) fileManager.revokeObjectURL(path);
49+
image.resize(blob || realPath, toImage, {blob: blob ? true : false, width: img.size, height: Math.round(img.size * (img.type ? ratios[img.type][forceSize] : 1.5)), quality: 95, fit: img.type ? fit[img.type] : 'inside'}).then(function(){
5750

58-
if(typeof data[sha] == 'undefined') data[sha] = {lastAccess: app.time()};
51+
//if(blob) fileManager.revokeObjectURL(path);
5952

60-
data[sha].size = img.size;
53+
if(typeof data[sha] == 'undefined') data[sha] = {lastAccess: app.time()};
6154

62-
img.callback({cache: true, path: escapeBackSlash(addCacheVars(toImage, img.size, img.sha)), sha: sha}, img.vars);
55+
data[sha].size = img.size;
6356

64-
if(queuedImages.length > 0)
65-
{
66-
imagesWithoutSaving++;
57+
img.callback({cache: true, path: escapeBackSlash(addCacheVars(toImage, img.size, img.sha)), sha: sha}, img.vars);
6758

68-
process.nextTick(function() {
69-
processTheImageQueue();
70-
});
59+
checkImagesWithoutSaving();
60+
resolve();
7161

72-
if(imagesWithoutSaving > 50)
73-
{
74-
imagesWithoutSaving = 0;
75-
storage.setThrottle('cache', data);
76-
}
77-
}
78-
else
79-
{
80-
imagesWithoutSaving = 0;
81-
processingTheImageQueue = false;
62+
}).catch(function(){
8263

83-
storage.setThrottle('cache', data);
84-
}
64+
//if(blob) fileManager.revokeObjectURL(path);
8565

86-
}).catch(function(){
66+
img.callback({cache: true, path: escapeBackSlash(realPath), sha: sha}, img.vars);
8767

88-
//if(blob) fileManager.revokeObjectURL(path);
89-
90-
img.callback({cache: true, path: escapeBackSlash(realPath), sha: sha}, img.vars);
91-
92-
if(queuedImages.length > 0)
93-
{
94-
imagesWithoutSaving++;
95-
96-
process.nextTick(function() {
97-
processTheImageQueue();
98-
});
99-
100-
if(imagesWithoutSaving > 50)
101-
{
102-
imagesWithoutSaving = 0;
103-
storage.setThrottle('cache', data);
104-
}
105-
}
106-
else
107-
{
108-
imagesWithoutSaving = 0;
109-
processingTheImageQueue = false;
68+
checkImagesWithoutSaving();
69+
resolve();
11070

111-
storage.setThrottle('cache', data);
112-
}
71+
});
11372

11473
});
11574
}
@@ -121,44 +80,42 @@ async function addImageToQueue(file, size, sha, callback, vars, type, forceSize)
12180
if(inArray(extention, imageExtensions.convert)) // Convert unsupported images
12281
await workers.convertImage(file);
12382

124-
queuedImages.push({file: file, size: size, sha: sha, callback: callback, vars: vars, type: type, forceSize: forceSize});
125-
126-
if(!processingTheImageQueue && !stopTheImageQueue)
127-
{
128-
processingTheImageQueue = true;
83+
threads.job('cache', {useThreads: 0.49}, processTheImageQueue, {file: file, size: size, sha: sha, callback: callback, vars: vars, type: type, forceSize: forceSize});
12984

130-
setTimeout(function(){
85+
threads.end('cache', function(){
13186

132-
process.nextTick(function() {
133-
processTheImageQueue();
134-
});
87+
imagesWithoutSaving = 0;
88+
storage.setThrottle('cache', data);
13589

136-
}, 0);
137-
}
90+
});
13891
}
13992

140-
var stopTheImageQueue = false;
141-
14293
function stopQueue()
14394
{
144-
stopTheImageQueue = true;
95+
threads.stop('cache');
14596
}
14697

14798
function resumeQueue()
14899
{
149-
stopTheImageQueue = false;
100+
threads.resume('cache');
101+
}
150102

151-
if(!processingTheImageQueue && queuedImages.length > 0)
152-
{
153-
processingTheImageQueue = true;
103+
function cleanQueue()
104+
{
105+
threads.clean('cache');
106+
if(data !== false) storage.setThrottle('cache', data);
107+
}
154108

155-
setTimeout(function(){
109+
var imagesWithoutSaving = 0;
156110

157-
process.nextTick(function() {
158-
processTheImageQueue();
159-
});
111+
function checkImagesWithoutSaving()
112+
{
113+
imagesWithoutSaving++;
160114

161-
}, 0);
115+
if(imagesWithoutSaving > 50)
116+
{
117+
imagesWithoutSaving = 0;
118+
storage.setThrottle('cache', data);
162119
}
163120
}
164121

@@ -235,12 +192,6 @@ function addImageVars(image)
235192
return image.path+(vars.length ? '?'+vars.join('&') : '');
236193
}
237194

238-
function cleanQueue()
239-
{
240-
queuedImages.splice(1, queuedImages.length - 1);
241-
if(data !== false) storage.setThrottle('cache', data);
242-
}
243-
244195
var data = false;
245196

246197
function returnThumbnailsImages(images, callback, file = false)
@@ -890,8 +841,6 @@ module.exports = {
890841
imageSizeSha: imageSizeSha,
891842
addImageVars: addImageVars,
892843
jsonMemory: function(){return jsonMemory},
893-
queuedImages: function(){return queuedImages},
894-
processingTheImageQueue: function(){return processingTheImageQueue},
895844
stopQueue: stopQueue,
896845
resumeQueue: resumeQueue,
897846
purge: purge,

‎scripts/opencomic.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ const app = require(p.join(appDir, 'scripts/app.js')),
424424
events = require(p.join(appDir, 'scripts/events.js')),
425425
ebook = require(p.join(appDir, 'scripts/ebook.js')),
426426
workers = require(p.join(appDir, 'scripts/workers.js')),
427+
threads = require(p.join(appDir, 'scripts/threads.js')),
427428
fileManager = require(p.join(appDir, 'scripts/file-manager.js')),
428429
serverClient = require(p.join(appDir, 'scripts/server-client.js')),
429430
reading = require(p.join(appDir, 'scripts/reading.js')),

‎scripts/threads.js‎

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
var threads = false;
2+
var threadsList = {};
3+
var queue = {default: []};
4+
var queueIsStop = {default: false};
5+
var onEnd = {};
6+
7+
function job(key = 'default', options = {}, callback = false)
8+
{
9+
const _arguments = [];
10+
11+
for(let i = 3, len = arguments.length; i < len; i++)
12+
{
13+
_arguments.push(arguments[i]);
14+
}
15+
16+
let resolve = false;
17+
let reject = false;
18+
19+
const promise = new Promise(function(_resolve, _reject) {
20+
21+
resolve = _resolve;
22+
reject = _reject;
23+
24+
});
25+
26+
const _promise = {
27+
promise: promise,
28+
resolve: resolve,
29+
reject: reject,
30+
};
31+
32+
addToQueue(key, options, callback, _arguments, _promise);
33+
34+
return promise;
35+
}
36+
37+
function addToQueue(key, options, callback, _arguments, promise)
38+
{
39+
if(!queue[key]) queue[key] = [];
40+
41+
if(options.priorize)
42+
{
43+
queue[key].unshift({
44+
options: options,
45+
callback: callback,
46+
arguments: _arguments,
47+
promise: promise,
48+
});
49+
}
50+
else
51+
{
52+
queue[key].push({
53+
options: options,
54+
callback: callback,
55+
arguments: _arguments,
56+
promise: promise,
57+
});
58+
}
59+
60+
processQueue(key);
61+
}
62+
63+
function processQueue(key = 'default')
64+
{
65+
if(!threads)
66+
threads = os.cpus().length || 1;
67+
68+
if(queueIsStop[key])
69+
return;
70+
71+
for(let thread = 0; thread < threads; thread++)
72+
{
73+
const status = processJob(key, thread);
74+
if(status === null) break;
75+
}
76+
}
77+
78+
function processJob(key = 'default', _thread = 0)
79+
{
80+
if(queueIsStop[key])
81+
return;
82+
83+
const thread = getThread(key, _thread);
84+
85+
if(thread.busy)
86+
return false;
87+
88+
const _queue = queue[key];
89+
90+
if(!_queue.length)
91+
{
92+
checkEnd(key);
93+
return null;
94+
}
95+
96+
const job = _queue.shift();
97+
98+
if(job.options.useThreads < 1 && _thread !== 0)
99+
{
100+
const useThreads = _thread / threads;
101+
102+
if(useThreads > job.options.useThreads)
103+
{
104+
_queue.unshift(job);
105+
return null;
106+
}
107+
}
108+
109+
thread.busy = true;
110+
thread.currentJob = job;
111+
startJob(thread);
112+
}
113+
114+
async function startJob(thread)
115+
{
116+
const result = await thread.currentJob.callback(...thread.currentJob.arguments);
117+
118+
thread.currentJob.promise.resolve(result);
119+
thread.currentJob = false;
120+
thread.busy = false;
121+
122+
processJob(thread.key, thread.thread);
123+
}
124+
125+
function getThread(key = 'default', thread = 0)
126+
{
127+
if(!threadsList[key]) threadsList[key] = {};
128+
129+
if(threadsList[key][thread])
130+
return threadsList[key][thread];
131+
132+
threadsList[key][thread] = {
133+
key: key,
134+
thread: thread,
135+
busy: false,
136+
currentJob: false,
137+
};
138+
139+
return threadsList[key][thread];
140+
}
141+
142+
function stop(key = 'default')
143+
{
144+
queueIsStop[key] = true;
145+
}
146+
147+
function resume(key = 'default')
148+
{
149+
queueIsStop[key] = false;
150+
processQueue(key);
151+
}
152+
153+
function clean(key = 'default')
154+
{
155+
queue[key] = [];
156+
}
157+
158+
function checkEnd(key = 'default')
159+
{
160+
if(onEnd[key])
161+
{
162+
onEnd[key]();
163+
onEnd[key] = false;
164+
}
165+
}
166+
167+
function end(key, callback)
168+
{
169+
onEnd[key] = callback;
170+
}
171+
172+
module.exports = {
173+
job: job,
174+
stop: stop,
175+
resume: resume,
176+
clean: clean,
177+
end: end,
178+
}

0 commit comments

Comments
 (0)