fix: task slicing limit calculation

This commit is contained in:
2026-06-05 16:48:24 +08:00
parent a4d47d8b85
commit 789f341aeb
3 changed files with 35 additions and 49 deletions
+21 -23
View File
@@ -173,11 +173,11 @@
<div class="inline-flex"> <div class="inline-flex">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<label for="offset">Offset</label> <label for="offset">Offset</label>
<input id="offset" class="slicing local-only pure-u-23-24" type="number" min="0" disabled/> <input id="offset" class="slicing local-only pure-u-23-24" type="number" min="0" value="0" disabled/>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<label for="limit">Max. (limit)</label> <label for="limit">Max. (limit)</label>
<input id="limit" class="slicing local-only pure-u-23-24" type="number" readonly disabled/> <input id="limit" class="slicing local-only pure-u-23-24" type="number" min="0" value="0" readonly disabled/>
</div> </div>
</div> </div>
</div> </div>
@@ -188,8 +188,8 @@
<script type="module"> <script type="module">
import { default as $, LogRecord, Rpc2 } from '/'; import { default as $, LogRecord, Rpc2 } from '/';
let limit = 0; let major = 0;
let length = 0; let minor = 0;
let busy = false; let busy = false;
let status = null; let status = null;
let locale = null; let locale = null;
@@ -199,7 +199,7 @@ let filereader = null;
let subcategories = []; let subcategories = [];
window.addEventListener('beforeunload', (e) => { window.addEventListener('beforeunload', (e) => {
writable?.abort().catch(() => void 0); writable?.abort();
Rpc2.notify('exit'); Rpc2.notify('exit');
e.preventDefault(); e.preventDefault();
e.returnValue = ''; e.returnValue = '';
@@ -234,7 +234,7 @@ async function handlePrimaryButtonClick() {
let [file, buffer] = await filereader().catch(e => alert('(ERROR) ' + new String(e ?? 'Unable to read data'))); let [file, buffer] = await filereader().catch(e => alert('(ERROR) ' + new String(e ?? 'Unable to read data')));
writable = await handle.createWritable().catch(() => null); writable = await handle.createWritable().catch(() => null);
limit = await Rpc2.invoke('ready', file.name, new Uint8Array(buffer).toBase64()); major = await Rpc2.invoke('ready', file.name, new Uint8Array(buffer).toBase64());
$.get('#fileLabel').innerText = file.name; $.get('#fileLabel').innerText = file.name;
$.get('#fromLabel').innerText = parameters.account; $.get('#fromLabel').innerText = parameters.account;
$.all('.columns').forEach(e => e.dispatchEvent(new Event('change'))); $.all('.columns').forEach(e => e.dispatchEvent(new Event('change')));
@@ -265,7 +265,7 @@ async function handlePrimaryButtonClick() {
if (options.slice) { if (options.slice) {
options.start = $('#offset').valueAsNumber * $('#chunksize').valueAsNumber + 2; options.start = $('#offset').valueAsNumber * $('#chunksize').valueAsNumber + 2;
options.limit = length; options.limit = Math.min(minor || major, $('#chunksize').valueAsNumber);
options.subcategories = subcategories.length > 0 ? subcategories: null; options.subcategories = subcategories.length > 0 ? subcategories: null;
} }
@@ -374,26 +374,19 @@ $.set('#groupby', 'change', (e) => {
$.set('#subcategory', 'change', (e) => { $.set('#subcategory', 'change', (e) => {
if (!unique) return; if (!unique) return;
subcategories = Array.from(e.target.options).filter(o => o.selected).map(o => o.value); subcategories = Array.from(e.target.options).filter(o => o.selected).map(o => o.value);
length = Object.entries(unique).filter(([k]) => subcategories.includes(k)).reduce((o, [_, v]) => o + v, 0); minor = Object.entries(unique).filter(([k]) => subcategories.includes(k)).reduce((o, [_, v]) => o + v, 0);
$('#chunksize').dispatchEvent(new Event('change')); $('#chunksize').dispatchEvent(new Event('change'));
}); });
$.set('#chunksize', 'change', (e) => { $.set('#chunksize', 'change', (e) => {
let n = length || limit; let limit = minor || major - 1;
let size = Math.floor(n / e.target.valueAsNumber); let value = Math.floor(limit / e.target.valueAsNumber);
$('#limit').value = size; $('#limit').value = value;
$('#offset').max = size; $('#offset').max = value;
$('#offset').value = 0; $('#offset').value = 0;
$('#offset').dispatchEvent(new Event('change')); $('#offset').dispatchEvent(new Event('change'));
}); });
$.set('#offset', 'change', (e) => {
let n = length || limit;
let size = $('#chunksize').valueAsNumber;
let offset = n - size * e.target.valueAsNumber;
length = Math.min(size, offset);
});
for (let item of Array.from(locales)) { for (let item of Array.from(locales)) {
$('#locale').add(new Option(item.name.toUpperCase(), item.name)); $('#locale').add(new Option(item.name.toUpperCase(), item.name));
$('#locale').dispatchEvent(new Event('change')); $('#locale').dispatchEvent(new Event('change'));
@@ -455,16 +448,21 @@ while (await new Promise(o => setTimeout(o, 1000, true))) {
$('#send').classList.add('pulse'); $('#send').classList.add('pulse');
$('#timezoneLabel').innerText = Temporal.Now.zonedDateTimeISO(locale.timezone).toLocaleString(locale.name); $('#timezoneLabel').innerText = Temporal.Now.zonedDateTimeISO(locale.timezone).toLocaleString(locale.name);
let { done, email, subject, recipient } = await Rpc2.invoke('progress').catch(() => new Object()); let { done, skip, email, subject, recipient } = await Rpc2.invoke('progress').catch(() => new Object());
$('#progressLabel').innerText = `${parseFloat(done / (length || limit) * 100).toFixed(2)}% (${done}/${length || limit})` let index = done + skip;
let limit = minor || major - 1;
let chunk = $('#chunksize').valueAsNumber;
let total = $('#slice').checked ? Math.min(chunk, limit - chunk * $('#offset').valueAsNumber) : limit;
$('#progressLabel').innerText = `${parseFloat(index / total * 100).toFixed(2)}% (${index}/${total})`
$('#subjectLabel').innerText = subject ?? ''; $('#subjectLabel').innerText = subject ?? '';
$('#recipientLabel').innerText = recipient ? `${recipient} <${email}>` : email ?? ''; $('#recipientLabel').innerText = recipient ? `${recipient} <${email}>` : email ?? '';
let uptime = await Rpc2.invoke('uptime').catch(() => 0); let uptime = await Rpc2.invoke('uptime').catch(() => 0);
$('#uptimeLabel').innerText = Temporal.Duration.from({ seconds: uptime }).round({ largestUnit: 'hours' }).toLocaleString('en', { style: 'digital' }); $('#uptimeLabel').innerText = Temporal.Duration.from({ seconds: uptime }).round({ largestUnit: 'hours' }).toLocaleString('en', { style: 'digital' });
let rate = done / uptime let rate = index / uptime
let remaining = done ? Math.floor(((length || limit) - done) / rate) : 0; let remaining = index ? Math.floor((total - index) / rate) : 0;
$('#remainingLabel').innerText = remaining ? `${Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en')} (${rate.toFixed(2)}/min)` : ''; $('#remainingLabel').innerText = remaining ? `${Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en')} (${rate.toFixed(2)}/min)` : '';
break; break;
case 'STANDBY': case 'STANDBY':
+14 -26
View File
@@ -283,9 +283,9 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
flow.append(Skip) flow.append(Skip)
sp.set('actions', lambda: flow.capabilities()) sp.set('actions', lambda: flow.capabilities())
sp.set('cancel', lambda: flow.do(Cancel, force=True)) sp.set('cancel', lambda: flow.queue(Cancel))
sp.set('close', lambda: flow.do(Close, force=True)) sp.set('close', lambda: flow.queue(Close))
sp.set('skip', lambda: flow.do(Skip, force=True)) sp.set('skip', lambda: flow.queue(Skip))
def loads(name: str, b64: str): def loads(name: str, b64: str):
from base64 import b64decode from base64 import b64decode
@@ -353,9 +353,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
while True: while True:
try: try:
flow.allow(Wait) flow.react(Wait)
flow.do(Wait)
flow.react()
progress.clear() progress.clear()
driver.switch_to.window(driver.window_handles[1]) driver.switch_to.window(driver.window_handles[1])
subject = get_subject() subject = get_subject()
@@ -377,16 +375,13 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
logger.info('Timezone: %s', locale.timezone) logger.info('Timezone: %s', locale.timezone)
progress['done'] = 0 progress['done'] = 0
progress['skip'] = 0
progress['subject'] = subject progress['subject'] = subject
logger.info('Done') logger.info('Done')
except Cancel: except Cancel:
status = Status.IDLE status = Status.IDLE
driver.switch_to.window(driver.window_handles[0]) driver.switch_to.window(driver.window_handles[0])
continue continue
except StopIteration:
logger.error('Invalid locale: %s', options.get('locale'))
status = Status.IDLE
continue
except Exception as e: except Exception as e:
logger.error('Error while loading data', exc_info=e) logger.error('Error while loading data', exc_info=e)
status = Status.IDLE status = Status.IDLE
@@ -399,8 +394,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
while index <= limit: while index <= limit:
try: try:
flow.do(Wait) flow.react(Wait)
flow.react()
attempts += 1 attempts += 1
name = cell(index, column.recipient).value name = cell(index, column.recipient).value
email = cell(index, column.email).value email = cell(index, column.email).value
@@ -447,8 +441,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
flow.allow(Cancel) flow.allow(Cancel)
flow.allow(Skip) flow.allow(Skip)
flow.do(Wait) flow.react(Wait)
flow.react()
progress['email'] = str(email) progress['email'] = str(email)
progress['recipient'] = str(name) progress['recipient'] = str(name)
logger.info('[%d/%d] Sending to %s', index-1, limit-1, email) logger.info('[%d/%d] Sending to %s', index-1, limit-1, email)
@@ -464,8 +457,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
if error is not None and not options.get('force'): if error is not None and not options.get('force'):
raise error raise error
flow.do(Wait) flow.react(Wait)
flow.react()
click("ul.classic-toolbar button[aria-label='Edit copy']") click("ul.classic-toolbar button[aria-label='Edit copy']")
until(ready) until(ready)
locate("div.io-ox-mail-compose-window iframe", condition=EC.frame_to_be_available_and_switch_to_it) locate("div.io-ox-mail-compose-window iframe", condition=EC.frame_to_be_available_and_switch_to_it)
@@ -495,8 +487,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
clean = target.text == hello clean = target.text == hello
driver.switch_to.default_content() driver.switch_to.default_content()
flow.do(Wait) flow.react(Wait)
flow.react()
click("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input") click("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input")
box = locate("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input input.token-input.tt-input[tabindex='0']") box = locate("div.io-ox-mail-compose-window div[data-extension-id='to'] > div.mail-input input.token-input.tt-input[tabindex='0']")
box.send_keys(str(email) + Keys.ENTER) box.send_keys(str(email) + Keys.ENTER)
@@ -514,8 +505,7 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
target.send_keys(template) target.send_keys(template)
clean = target.get_attribute('value') == template clean = target.get_attribute('value') == template
flow.do(Wait) flow.react(Wait)
flow.react()
box = locate("div.io-ox-mail-compose-window .mail-input .tokenfield .token") box = locate("div.io-ox-mail-compose-window .mail-input .tokenfield .token")
recipient = box.get_attribute('innerText').strip() recipient = box.get_attribute('innerText').strip()
@@ -527,10 +517,10 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
logger.warning('Malformed email content detected; retrying (%d)...', attempts) logger.warning('Malformed email content detected; retrying (%d)...', attempts)
continue continue
flow.do(Wait) flow.react(Wait)
flow.react()
click("div.io-ox-mail-compose-window button[data-action='send']") click("div.io-ox-mail-compose-window button[data-action='send']")
except Skip: except Skip:
progress['skip'] += 1
index += 1 index += 1
attempts = 0 attempts = 0
continue continue
@@ -566,13 +556,11 @@ def main(driver: Chrome, logger = logging.getLogger('main')):
if options.get('writable'): if options.get('writable'):
try: try:
logger.info('Saving document...')
status = Status.CLOSING status = Status.CLOSING
flow.allow(Wait, False) flow.allow(Wait, False)
flow.allow(Timeout)
flow.allow(Close) flow.allow(Close)
logger.info('Saving document...') flow.react(Timeout)
flow.do(Timeout, force=True)
flow.react()
except Close: except Close:
pass pass
except Exception as e: except Exception as e:
BIN
View File
Binary file not shown.