Skip to content

Commit 5c50739

Browse files
authored
New: Improved extract performance of big files
1 parent e3b2cfa commit 5c50739

3 files changed

Lines changed: 193 additions & 78 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2929
- Change extraction of `RAR` and `TAR` to 7zip to improve performance [`c0c2d6d`](https://github.com/ollm/OpenComic/commit/c0c2d6d61016241e70d4ee475e8d19567b8e71db)
3030
- Add suport for compressed `LZH`, `ACE`, `TAR.GZ`, `TAR.XZ`, `TAR.BZIP2` and `TAR.ZSTD` [`e7e7815`](https://github.com/ollm/OpenComic/commit/e7e7815d7a841c02354f9b5219fcdf348c10543b)
3131
- Support compressed files with password [`9a6ef8e`](https://github.com/ollm/OpenComic/commit/9a6ef8e0a363e72e98634e827c50189bb2841047)
32-
- Show release notes in new version dialog
32+
- Show release notes in new version dialog [`e572128`](https://github.com/ollm/OpenComic/commit/e572128aecad5811fb2f1a7b1690bbfe1ebabe82)
33+
- Improved extract performance of big files
3334

3435
##### 🐛 Bug Fixes
3536

scripts/file-manager.js

Lines changed: 181 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,6 +1685,106 @@ var fileCompressed = function(path, _realPath = false, forceType = false, prefix
16851685

16861686
}
16871687

1688+
this.getOptimalTask = function(fileSize, status, optimalFileSize, maxItems, numThreads) {
1689+
1690+
if(!status.tasks[status.current])
1691+
status.tasks[status.current] = {size: 0, items: 0};
1692+
1693+
let current = status.current;
1694+
let task = status.tasks[current];
1695+
1696+
if(!status.fullSize)
1697+
{
1698+
task.size += fileSize;
1699+
task.items++;
1700+
1701+
if(task.size > optimalFileSize)
1702+
status.current++;
1703+
1704+
if(status.current >= numThreads)
1705+
{
1706+
status.fullSize = true;
1707+
status.current = 0;
1708+
}
1709+
}
1710+
else if(!status.full)
1711+
{
1712+
let full = true;
1713+
1714+
for(let t = status.current; t < numThreads; t++)
1715+
{
1716+
task = status.tasks[t];
1717+
1718+
if(task.items >= maxItems)
1719+
continue;
1720+
1721+
status.current = t;
1722+
full = false;
1723+
1724+
break;
1725+
}
1726+
1727+
current = status.current;
1728+
status.current++;
1729+
1730+
task.size += fileSize;
1731+
task.items++;
1732+
1733+
if(full)
1734+
{
1735+
status.full = true;
1736+
status.current = numThreads;
1737+
}
1738+
else if(status.current >= numThreads)
1739+
{
1740+
status.current = 0;
1741+
}
1742+
}
1743+
else
1744+
{
1745+
task.size += fileSize;
1746+
task.items++;
1747+
1748+
if(task.items >= maxItems)
1749+
status.current++;
1750+
}
1751+
1752+
return current;
1753+
1754+
}
1755+
1756+
this.stackBySize = function(only, optimalFileSize = 30, maxItems = 100) {
1757+
1758+
if(!only)
1759+
return [only];
1760+
1761+
optimalFileSize *= 1024 * 1024; // In bytes
1762+
1763+
const numThreads = threads.num();
1764+
const tasks = [];
1765+
1766+
const status = {
1767+
tasks: {},
1768+
current: 0,
1769+
fullSize: false,
1770+
full: false,
1771+
};
1772+
1773+
for(let i = 0, len = only.length; i < len; i++)
1774+
{
1775+
const name = only[i];
1776+
const fileSize = fileSizes.get(p.join(this.path, name)) ?? (1024 * 100); // If the size is not known, it is considered to be 100KB.
1777+
1778+
const task = this.getOptimalTask(fileSize, status, optimalFileSize, maxItems, numThreads);
1779+
1780+
if(!tasks[task]) tasks[task] = [];
1781+
tasks[task].push(name);
1782+
}
1783+
1784+
return tasks;
1785+
1786+
}
1787+
16881788
this.isFullyWrittenToDisk = async function(path, realPath, prevDiskSize = 0, intent = 0) {
16891789

16901790
const fileSize = fileSizes.get(path) ?? 0;
@@ -1747,10 +1847,10 @@ var fileCompressed = function(path, _realPath = false, forceType = false, prefix
17471847

17481848
const files = [];
17491849

1750-
const _this = this;
1850+
const self = this;
17511851

17521852
const _7z = await this.open7z();
1753-
const readSome = false;
1853+
let readSome = false;
17541854

17551855
return new Promise(function(resolve, reject) {
17561856

@@ -1761,30 +1861,30 @@ var fileCompressed = function(path, _realPath = false, forceType = false, prefix
17611861
if(/^D/.test(data.attributes) && !data.size) // Ignore directories
17621862
return;
17631863

1764-
const originalName = _this.removeTmp(p.normalize(data.file));
1765-
const name = _this.fixUnsupportedCharsInWindows(originalName);
1864+
const originalName = self.removeTmp(p.normalize(data.file));
1865+
const name = self.fixUnsupportedCharsInWindows(originalName);
17661866
const same = originalName === name ? true : false;
17671867

1768-
files.push({name: name, fixedName: (!same ? name : ''), originalName: (!same ? originalName : ''), path: p.join(_this.path, name), fileSize: data.size});
1769-
_this.setFileStatus(name, {extracted: false});
1868+
files.push({name: name, fixedName: (!same ? name : ''), originalName: (!same ? originalName : ''), path: p.join(self.path, name), fileSize: data.size});
1869+
self.setFileStatus(name, {extracted: false});
17701870

17711871
readSome = true;
17721872
}
17731873

17741874
}).on('end', function(data) {
17751875

1776-
_this.files = _this.filesToMultidimension(files);
1777-
resolve(_this.files);
1876+
self.files = self.filesToMultidimension(files);
1877+
resolve(self.files);
17781878

17791879
}).on('error', function(error){
17801880

17811881
if(readSome)
17821882
{
1783-
/*_this.files = _this.filesToMultidimension(files);
1784-
resolve(_this.files);*/
1883+
/*self.files = self.filesToMultidimension(files);
1884+
resolve(self.files);*/
17851885

1786-
//_this.saveErrorToCache(error);
1787-
dom.compressedError(error, false, sha1(_this.path));
1886+
//self.saveErrorToCache(error);
1887+
dom.compressedError(error, false, sha1(self.path));
17881888
}
17891889
else
17901890
{
@@ -1799,7 +1899,7 @@ var fileCompressed = function(path, _realPath = false, forceType = false, prefix
17991899

18001900
this.extract7z = async function() {
18011901

1802-
const _this = this;
1902+
const self = this;
18031903

18041904
const only = [];
18051905
const extractName = {};
@@ -1819,117 +1919,123 @@ var fileCompressed = function(path, _realPath = false, forceType = false, prefix
18191919
}
18201920
}
18211921

1822-
const tasks = this.stackOnlyInTasks(only || false, 100);
1823-
1824-
let result = false;
1825-
18261922
this.progressIndex = 1;
18271923

1924+
const tasks = this.stackBySize(only || false, 30, 100);
1925+
const promises = [];
1926+
18281927
for(let i = 0, len = tasks.length; i < len; i++)
18291928
{
18301929
const onlyStack = tasks[i];
1831-
const _7z = await this.open7z(true, onlyStack);
18321930

18331931
let extractedSome = false;
18341932
let hasError = false;
18351933

1836-
result = await new Promise(function(resolve, reject) {
1934+
promises.push(threads.job('extractUsingThreads', {useThreads: 1}, async function() {
18371935

1838-
const waitDisk = [];
1936+
return new Promise(async function(resolve, reject) {
18391937

1840-
_7z.on('data', function(data) {
1938+
const waitDisk = [];
1939+
const _7z = await self.open7z(true, onlyStack);
18411940

1842-
const extract = data.status == 'extracted' ? true : false;
1941+
_7z.on('data', function(data) {
18431942

1844-
if(extract)
1845-
{
1846-
waitDisk.push(new Promise(async function(_resolve) {
1943+
const extract = data.status == 'extracted' ? true : false;
18471944

1848-
_this.setProgress(_this.progressIndex++ / onlyLen);
1945+
if(extract)
1946+
{
1947+
waitDisk.push(new Promise(async function(_resolve) {
18491948

1850-
let name = _this.removeTmp(p.normalize(data.file));
1851-
name = extractName[name] || name;
1949+
self.setProgress(self.progressIndex++ / onlyLen);
18521950

1853-
const path = p.join(_this.path, name);
1854-
const realPath = p.join(_this.tmp, name);
1951+
let name = self.removeTmp(p.normalize(data.file));
1952+
name = extractName[name] || name;
18551953

1856-
await _this.isFullyWrittenToDisk(path, realPath);
1954+
const path = p.join(self.path, name);
1955+
const realPath = p.join(self.tmp, name);
18571956

1858-
_this.setFileStatus(name, {extracted: extract});
1859-
_this.whenExtractFile(path);
1957+
await self.isFullyWrittenToDisk(path, realPath);
18601958

1861-
extractedSome = true;
1862-
_resolve();
1959+
self.setFileStatus(name, {extracted: extract});
1960+
self.whenExtractFile(path);
18631961

1864-
}));
1865-
}
1962+
extractedSome = true;
1963+
_resolve();
18661964

1867-
}).on('progress', function(progress) {
1965+
}));
1966+
}
18681967

1869-
if(!onlyLen)
1870-
_this.setProgress(progress.percent / 100);
1968+
}).on('progress', function(progress) {
18711969

1872-
}).on('end', async function() {
1970+
if(!onlyLen)
1971+
self.setProgress(progress.percent / 100);
18731972

1874-
if(!hasError)
1875-
{
1876-
await Promise.all(waitDisk);
1877-
resolve();
1878-
}
1973+
}).on('end', async function() {
18791974

1880-
}).on('error', async function(error) {
1975+
if(!hasError)
1976+
{
1977+
await Promise.all(waitDisk);
1978+
resolve();
1979+
}
18811980

1882-
hasError = true;
1981+
}).on('error', async function(error) {
18831982

1884-
if(filePassword.check(error))
1885-
{
1886-
if(fs.existsSync(_this.tmp))
1887-
fs.rmSync(_this.tmp, {recursive: true});
1983+
hasError = true;
18881984

1889-
if(!_this.config.fromThumbnailsGeneration)
1985+
if(filePassword.check(error))
18901986
{
1891-
const password = await filePassword.request(_this.path);
1987+
if(fs.existsSync(self.tmp))
1988+
fs.rmSync(self.tmp, {recursive: true});
18921989

1893-
if(password)
1990+
if(!self.config.fromThumbnailsGeneration)
18941991
{
1895-
if(!fs.existsSync(_this.tmp))
1896-
fs.mkdirSync(_this.tmp);
1897-
1898-
resolve(_this.extract7z());
1992+
const password = await filePassword.request(self.path);
1993+
1994+
if(password)
1995+
{
1996+
if(!fs.existsSync(self.tmp))
1997+
fs.mkdirSync(self.tmp);
1998+
1999+
resolve(self.extract7z());
2000+
}
2001+
else
2002+
{
2003+
self.rejectAllWhenExtractFile();
2004+
reject(error);
2005+
}
18992006
}
19002007
else
19012008
{
1902-
_this.rejectAllWhenExtractFile();
2009+
self.rejectAllWhenExtractFile();
19032010
reject(error);
19042011
}
19052012
}
2013+
else if(extractedSome)
2014+
{
2015+
self.saveErrorToCache(error);
2016+
dom.compressedError(error, false, sha1(self.path));
2017+
2018+
await Promise.all(waitDisk);
2019+
resolve();
2020+
}
19062021
else
19072022
{
1908-
_this.rejectAllWhenExtractFile();
19092023
reject(error);
19102024
}
1911-
}
1912-
else if(extractedSome)
1913-
{
1914-
_this.saveErrorToCache(error);
1915-
dom.compressedError(error, false, sha1(_this.path));
19162025

1917-
await Promise.all(waitDisk);
1918-
resolve();
1919-
}
1920-
else
1921-
{
1922-
reject(error);
1923-
}
2026+
});
19242027

19252028
});
19262029

1927-
});
2030+
}));
2031+
19282032
}
19292033

2034+
await Promise.all(promises);
2035+
19302036
this.setProgress(1);
19312037

1932-
return result;
2038+
return;
19332039
}
19342040

19352041

scripts/threads.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ var queueIsStop = {default: false};
55
var stats = {};
66
var onEnd = {};
77

8+
function num()
9+
{
10+
if(!threads)
11+
threads = os.cpus().length || 1;
12+
13+
return threads;
14+
}
15+
816
async function job(key = 'default', options = {}, callback = false)
917
{
1018
if(options.delay)
@@ -67,8 +75,7 @@ function addToQueue(key, options, callback, _arguments, promise)
6775

6876
function processQueue(key = 'default')
6977
{
70-
if(!threads)
71-
threads = os.cpus().length || 1;
78+
num();
7279

7380
if(queueIsStop[key])
7481
return;
@@ -232,6 +239,7 @@ function sumStats()
232239
}
233240

234241
module.exports = {
242+
num: num,
235243
job: job,
236244
stop: stop,
237245
resume: resume,

0 commit comments

Comments
 (0)