Skip to content

Commit 276f9c4

Browse files
committed
Update recalc logic to be resumable
1 parent bc1a8bc commit 276f9c4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2320
-1186
lines changed

qa-content/qa-admin.js

Lines changed: 99 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,76 +19,124 @@
1919
More about this license: http://www.question2answer.org/license.php
2020
*/
2121

22-
var qa_recalc_running = 0;
22+
const qa_recalcProcesses = new Map();
2323

24-
window.onbeforeunload = function(event)
25-
{
26-
if (qa_recalc_running > 0) {
27-
event = event || window.event;
28-
var message = qa_warning_recalc;
29-
event.returnValue = message;
30-
return message;
24+
window.onbeforeunload = event => {
25+
for (let [processKey, process] of qa_recalcProcesses.entries()) {
26+
if (process.clientRunning) {
27+
event.preventDefault();
28+
event.returnValue = true;
29+
}
3130
}
3231
};
3332

34-
function qa_recalc_click(state, elem, value, noteid)
33+
/**
34+
* @param {String} processKey
35+
* @param {object} options - See keys and default values below
36+
* @returns {boolean}
37+
*/
38+
function qa_recalc_click(processKey, options = {})
3539
{
36-
if (elem.qa_recalc_running) {
37-
elem.qa_recalc_stopped = true;
38-
40+
options = {
41+
forceRestart: false,
42+
requiresServerTracking: true,
43+
callbackStart: process => {},
44+
callbackStop: hasFinished => {},
45+
...options,
46+
};
47+
48+
let process = qa_recalcProcesses.get(processKey) ?? {processKey: processKey};
49+
50+
const startButton = document.getElementById(processKey);
51+
const continueButton = document.getElementById(processKey + '_continue');
52+
const statusLabel = document.getElementById(processKey + '_status');
53+
54+
if (process.clientRunning) {
55+
process.stopRequest = true;
3956
} else {
40-
elem.qa_recalc_running = true;
41-
elem.qa_recalc_stopped = false;
42-
qa_recalc_running++;
57+
process = {
58+
...process,
59+
"startButton": startButton,
60+
"continueButton": continueButton,
61+
"statusLabel": statusLabel,
62+
"clientRunning": true,
63+
"stopRequest": false,
64+
"options": options
65+
};
66+
67+
qa_recalcProcesses.set(processKey, process);
4368

44-
document.getElementById(noteid).innerHTML = '';
45-
elem.qa_original_value = elem.value;
46-
if (value)
47-
elem.value = value;
69+
qa_conceal(process.continueButton);
4870

49-
qa_recalc_update(elem, state, noteid);
71+
statusLabel.innerHTML = qa_langs.please_wait;
72+
startButton.value = qa_langs.process_stop;
73+
74+
process.options.callbackStart(process);
75+
76+
qa_recalc_update(process);
5077
}
5178

5279
return false;
5380
}
5481

55-
function qa_recalc_update(elem, state, noteid)
82+
function qa_recalc_update(process)
5683
{
57-
if (state) {
58-
var recalcCode = elem.form.elements.code_recalc ? elem.form.elements.code_recalc.value : elem.form.elements.code.value;
59-
qa_ajax_post(
60-
'recalc',
61-
{state: state, code: recalcCode},
62-
function(lines) {
63-
if (lines[0] == '1') {
64-
if (lines[2])
65-
document.getElementById(noteid).innerHTML = lines[2];
66-
67-
if (elem.qa_recalc_stopped)
68-
qa_recalc_cleanup(elem);
69-
else
70-
qa_recalc_update(elem, lines[1], noteid);
71-
72-
} else if (lines[0] == '0') {
73-
document.getElementById(noteid).innerHTML = lines[1];
74-
qa_recalc_cleanup(elem);
75-
76-
} else {
84+
const recalcCode = process.startButton.form.elements.code.value;
85+
86+
qa_ajax_post(
87+
'recalc',
88+
{
89+
process: process.processKey,
90+
forceRestart: process.options.forceRestart,
91+
code: recalcCode
92+
},
93+
function (lines) {
94+
const result = lines[0] ?? null;
95+
const message = lines[1] ?? null;
96+
const hasFinished = (lines[2] ?? '0') === '1';
97+
98+
switch (result) {
99+
case '1':
100+
if (message !== null) {
101+
process.statusLabel.innerHTML = message;
102+
}
103+
104+
process.serverProcessPending = process.options.requiresServerTracking ? !hasFinished : false;
105+
if (hasFinished || process.stopRequest) {
106+
qa_recalc_cleanup(process, hasFinished);
107+
} else {
108+
process.options.forceRestart = false;
109+
qa_recalc_update(process);
110+
}
111+
break;
112+
case '0':
113+
process.statusLabel.innerHTML = message;
114+
process.serverProcessPending = true;
115+
qa_recalc_cleanup(process, false, message);
116+
break;
117+
default:
118+
process.serverProcessPending = true;
119+
qa_recalc_cleanup(process);
77120
qa_ajax_error();
78-
qa_recalc_cleanup(elem);
79-
}
80121
}
81-
);
82-
} else {
83-
qa_recalc_cleanup(elem);
84-
}
122+
}
123+
);
85124
}
86125

87-
function qa_recalc_cleanup(elem)
126+
function qa_recalc_cleanup(process, hasFinished = false, message = null)
88127
{
89-
elem.value = elem.qa_original_value;
90-
elem.qa_recalc_running = null;
91-
qa_recalc_running--;
128+
process.clientRunning = false;
129+
130+
process.options.callbackStop(hasFinished);
131+
132+
if (process.options.requiresServerTracking && process.serverProcessPending) {
133+
process.startButton.value = qa_langs.process_restart;
134+
process.statusLabel.innerHTML = message ?? qa_langs.process_unfinished;
135+
qa_reveal(process.continueButton);
136+
} else {
137+
process.startButton.value = qa_langs.process_start;
138+
qa_conceal(process.continueButton);
139+
}
92140
}
93141

94142
function qa_mailing_start(noteid, pauseid)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
/*
4+
Question2Answer by Gideon Greenspan and contributors
5+
http://www.question2answer.org/
6+
7+
This program is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU General Public License
9+
as published by the Free Software Foundation; either version 2
10+
of the License, or (at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
More about this license: http://www.question2answer.org/license.php
18+
*/
19+
20+
abstract class Q2A_Admin_Recalc_AbstractProcessManager
21+
{
22+
/** @var string */
23+
protected $stateOption;
24+
25+
/** @var int */
26+
protected $currentStepIndex = 0;
27+
28+
/** @var array */
29+
protected $steps;
30+
31+
/**
32+
* @return array Return step and process state data
33+
*/
34+
public function execute($forceRestart = false)
35+
{
36+
$newStep = false;
37+
38+
try {
39+
if ($forceRestart) {
40+
throw new Exception('Force exception');
41+
}
42+
43+
$step = $this->loadState();
44+
45+
if ($step->isFinished()) {
46+
$this->currentStepIndex++;
47+
48+
if ($this->currentStepIndex >= count($this->steps)) {
49+
$this->clearState();
50+
51+
return [
52+
'process_finished' => true,
53+
'message' => qa_lang('admin/process_complete'),
54+
];
55+
}
56+
57+
$newStep = true;
58+
$step = $this->getCurrentStepInstance();
59+
}
60+
} catch (Exception $e) {
61+
$step = $this->getCurrentStepInstance();
62+
$newStep = true;
63+
}
64+
65+
if ($newStep) {
66+
$step->setup();
67+
} else {
68+
$step->execute();
69+
}
70+
71+
$result = [
72+
'step_state' => $step->asArray(),
73+
'step_index' => $this->currentStepIndex,
74+
'process_finished' => false,
75+
];
76+
$this->saveState($result);
77+
78+
$result['message'] = $step->getMessage();
79+
80+
return $result;
81+
}
82+
83+
/**
84+
* @throws Exception
85+
*/
86+
private function loadState()
87+
{
88+
$state = qa_opt($this->stateOption);
89+
$state = json_decode($state, true);
90+
91+
if (!isset($state['step_index'])) {
92+
throw new Exception('Nothing to load');
93+
}
94+
95+
$this->currentStepIndex = $state['step_index'];
96+
97+
$step = $this->getCurrentStepInstance();
98+
$step->loadFromJson($state['step_state']);
99+
100+
return $step;
101+
}
102+
103+
private function saveState($state)
104+
{
105+
qa_opt($this->stateOption, json_encode($state));
106+
}
107+
108+
private function clearState()
109+
{
110+
qa_opt($this->stateOption, '');
111+
}
112+
113+
/**
114+
* @return Q2A_Admin_Recalc_AbstractStep
115+
*/
116+
protected function getCurrentStepInstance()
117+
{
118+
// Make sure the step index to instantiate is valid
119+
$this->currentStepIndex = min(max(0, $this->currentStepIndex), count($this->steps) - 1);
120+
121+
return new $this->steps[$this->currentStepIndex];
122+
}
123+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
Question2Answer by Gideon Greenspan and contributors
5+
http://www.question2answer.org/
6+
7+
This program is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU General Public License
9+
as published by the Free Software Foundation; either version 2
10+
of the License, or (at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
More about this license: http://www.question2answer.org/license.php
18+
*/
19+
20+
abstract class Q2A_Admin_Recalc_AbstractStep
21+
{
22+
/** @var int|null */
23+
protected $processedItems = 0;
24+
25+
/** @var int|null */
26+
protected $totalItems;
27+
28+
/** @var mixed */
29+
protected $nextItemId = 0;
30+
31+
/** @var mixed */
32+
protected $lastProcessedItemId = 0;
33+
34+
/** @var bool */
35+
protected $isFinished = false;
36+
37+
/** @var string */
38+
protected $messageLangId = '';
39+
40+
abstract public function setup();
41+
42+
abstract public function execute();
43+
44+
/**
45+
* @return array
46+
*/
47+
public function asArray()
48+
{
49+
return [
50+
'is_finished' => $this->isFinished,
51+
'processed_items' => $this->processedItems,
52+
'total_items' => $this->totalItems,
53+
'next_item_id' => $this->nextItemId,
54+
'last_processed_item_id' => $this->lastProcessedItemId,
55+
];
56+
}
57+
58+
public function loadFromJson($state)
59+
{
60+
$this->isFinished = $state['is_finished'];
61+
$this->processedItems = $state['processed_items'];
62+
$this->totalItems = $state['total_items'];
63+
$this->nextItemId = $state['next_item_id'];
64+
$this->lastProcessedItemId = $state['last_processed_item_id'];
65+
}
66+
67+
/**
68+
* @return bool
69+
*/
70+
public function isFinished()
71+
{
72+
return $this->isFinished;
73+
}
74+
75+
public function getMessage()
76+
{
77+
require_once QA_INCLUDE_DIR . 'app/format.php';
78+
79+
return strtr(qa_lang($this->messageLangId), array(
80+
'^1' => qa_format_number($this->processedItems),
81+
'^2' => qa_format_number($this->totalItems),
82+
));
83+
}
84+
}

0 commit comments

Comments
 (0)