Merge branch 'nonblockingReindex' into 'master'

reindexing in background and gravity cleanup

Closes #29

See merge request lukas/openmediacenter!20
This commit is contained in:
Lukas Heiligenbrunner 2020-10-21 19:14:46 +00:00
commit 8f7ba53c39
10 changed files with 163 additions and 40 deletions

View File

@ -1,18 +0,0 @@
<?php
$return = new stdClass();
if (file_exists("/tmp/output.log")) {
$out = file_get_contents("/tmp/output.log");
// clear log file
file_put_contents("/tmp/output.log", "");
$return->message = $out;
$return->contentAvailable = true;
if (substr($out, -strlen("-42")) == "-42") {
unlink("/tmp/output.log");
}
} else {
$return->contentAvailable = false;
}
echo json_encode($return);

View File

@ -1,7 +1,7 @@
<?php <?php
require_once './src/Database.php'; require_once 'Database.php';
require_once './src/TMDBMovie.php'; require_once 'TMDBMovie.php';
require_once './src/SSettings.php'; require_once 'SSettings.php';
/** /**
* Class VideoParser * Class VideoParser

View File

@ -1,5 +1,5 @@
<?php <?php
require_once 'src/Database.php'; require_once __DIR__.'/../Database.php';
abstract class RequestBase { abstract class RequestBase {
protected $conn; protected $conn;

View File

@ -1,5 +1,6 @@
<?php <?php
require_once 'RequestBase.php'; require_once 'RequestBase.php';
require_once __DIR__ . '/../VideoParser.php';
/** /**
* Class Settings * Class Settings
@ -9,6 +10,7 @@ class Settings extends RequestBase {
function initHandlers() { function initHandlers() {
$this->getFromDB(); $this->getFromDB();
$this->saveToDB(); $this->saveToDB();
$this->reIndexHandling();
} }
/** /**
@ -102,4 +104,57 @@ class Settings extends RequestBase {
} }
}); });
} }
/**
* methods for handling reindexing and cleanup of db gravity
*/
private function reIndexHandling() {
$this->addActionHandler("startReindex", function () {
$indexrunning = false;
if (file_exists("/tmp/output.log")) {
$out = file_get_contents("/tmp/output.log");
if (substr($out, -strlen("-42")) == "-42") {
unlink("/tmp/output.log");
} else {
$indexrunning = true;
}
}
if (!$indexrunning) {
// start extraction of video previews in background
$cmd = 'php extractvideopreviews.php';
exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, '/dev/zero', '/tmp/openmediacenterpid'));
$this->commitMessage('{"success": true}');
} else {
$this->commitMessage('{"success": false}');
}
});
$this->addActionHandler("cleanupGravity", function () {
$vp = new VideoParser();
$vp->cleanUpGravity();
});
$this->addActionHandler("getStatusMessage", function () {
$return = new stdClass();
if (file_exists("/tmp/output.log")) {
$out = file_get_contents("/tmp/output.log");
// clear log file
file_put_contents("/tmp/output.log", "");
$return->message = $out;
$return->contentAvailable = true;
if (substr($out, -strlen("-42")) == "-42") {
unlink("/tmp/output.log");
}
} else {
$return->contentAvailable = false;
}
$this->commitMessage(json_encode($return));
});
}
} }

View File

@ -1,5 +1,5 @@
<?php <?php
require_once 'src/SSettings.php'; require_once __DIR__.'/../SSettings.php';
require_once 'RequestBase.php'; require_once 'RequestBase.php';
/** /**

View File

@ -17,7 +17,7 @@
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --reporters=jest-junit --reporters=default", "test": "react-scripts test --reporters=jest-junit --reporters=default --ci --silent",
"coverage": "react-scripts test --coverage --watchAll=false", "coverage": "react-scripts test --coverage --watchAll=false",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },

View File

@ -16,9 +16,6 @@ class MovieSettings extends React.Component {
} }
componentDidMount() { componentDidMount() {
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000); this.myinterval = setInterval(this.updateStatus, 1000);
} }
@ -29,9 +26,12 @@ class MovieSettings extends React.Component {
render() { render() {
return ( return (
<> <>
<button disabled={this.state.startbtnDisabled} className='reindexbtn btn btn-success' onClick={() => { <button disabled={this.state.startbtnDisabled}
this.startReindex() className='btn btn-success'
}}>Reindex Movies onClick={() => {this.startReindex()}}>Reindex Movie
</button>
<button className='btn btn-warning'
onClick={() => {this.cleanupGravity()}}>Cleanup Gravity
</button> </button>
<div className={style.indextextarea}>{this.state.text.map(m => ( <div className={style.indextextarea}>{this.state.text.map(m => (
<div className='textarea-element'>{m}</div> <div className='textarea-element'>{m}</div>
@ -50,13 +50,19 @@ class MovieSettings extends React.Component {
this.setState({startbtnDisabled: true}); this.setState({startbtnDisabled: true});
console.log("starting"); console.log("starting");
const updateRequest = new FormData(); const request = new FormData();
request.append("action", "startReindex");
// fetch all videos available // fetch all videos available
fetch('/api/extractvideopreviews.php', {method: 'POST', body: updateRequest}) fetch('/api/settings.php', {method: 'POST', body: request})
.then((response) => response.text() .then((response) => response.json()
.then((result) => { .then((result) => {
// todo 2020-07-4: some kind of return finished handler console.log(result);
console.log("returned"); if (result.success) {
console.log("started successfully");
} else {
console.log("error, reindex already running");
this.setState({startbtnDisabled: true});
}
})) }))
.catch(() => { .catch(() => {
console.log("no connection to backend"); console.log("no connection to backend");
@ -71,8 +77,10 @@ class MovieSettings extends React.Component {
* This interval function reloads the current status of reindexing from backend * This interval function reloads the current status of reindexing from backend
*/ */
updateStatus = () => { updateStatus = () => {
const updateRequest = new FormData(); const request = new FormData();
fetch('/api/extractionData.php', {method: 'POST', body: updateRequest}) request.append("action", "getStatusMessage");
fetch('/api/settings.php', {method: 'POST', body: request})
.then((response) => response.json() .then((response) => response.json()
.then((result) => { .then((result) => {
if (result.contentAvailable === true) { if (result.contentAvailable === true) {
@ -94,6 +102,25 @@ class MovieSettings extends React.Component {
console.log("no connection to backend"); console.log("no connection to backend");
}); });
}; };
/**
* send request to cleanup db gravity
*/
cleanupGravity() {
const request = new FormData();
request.append("action", "cleanupGravity");
fetch('/api/settings.php', {method: 'POST', body: request})
.then((response) => response.text()
.then((result) => {
this.setState({
text: ['successfully cleaned up gravity!']
});
}))
.catch(() => {
console.log("no connection to backend");
});
}
} }
export default MovieSettings; export default MovieSettings;

View File

@ -9,5 +9,5 @@
overflow-x: auto; overflow-x: auto;
overflow-y: scroll; overflow-y: scroll;
padding: 10px; padding: 10px;
width: 50%; width: 40%;
} }

View File

@ -22,15 +22,33 @@ describe('<MovieSettings/>', function () {
}); });
it('test simulate reindex', function () { it('test simulate reindex', function () {
global.fetch = global.prepareFetchApi({}); global.fetch = global.prepareFetchApi({success: true});
const wrapper = shallow(<MovieSettings/>); const wrapper = shallow(<MovieSettings/>);
wrapper.find(".reindexbtn").simulate("click"); wrapper.find("button").findWhere(e => e.text() === "Reindex Movie" && e.type() === "button").simulate("click");
// initial send of reindex request to server // initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1); expect(global.fetch).toBeCalledTimes(1);
}); });
it('test failing reindex start', done => {
global.fetch = global.prepareFetchApi({success: false});
const wrapper = shallow(<MovieSettings/>);
wrapper.find("button").findWhere(e => e.text() === "Reindex Movie" && e.type() === "button").simulate("click");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
// reindex already running --> so disable startbdn
expect(wrapper.state()).toMatchObject({startbtnDisabled: true});
global.fetch.mockClear();
done();
});
});
it('content available received and in state', done => { it('content available received and in state', done => {
global.fetch = global.prepareFetchApi({ global.fetch = global.prepareFetchApi({
contentAvailable: true, contentAvailable: true,
@ -51,4 +69,44 @@ describe('<MovieSettings/>', function () {
done(); done();
}); });
}); });
it('test reindex with no content available', done=> {
global.fetch = global.prepareFetchApi({
contentAvailable: false
});
global.clearInterval = jest.fn();
const wrapper = shallow(<MovieSettings/>);
wrapper.instance().updateStatus();
process.nextTick(() => {
// expect the refresh interval to be cleared
expect(global.clearInterval).toBeCalledTimes(1);
// expect startbtn to be reenabled
expect(wrapper.state()).toMatchObject({startbtnDisabled: false});
global.fetch.mockClear();
done();
});
});
it('test simulate gravity cleanup', done => {
global.fetch = global.prepareFetchApi("mmi");
const wrapper = shallow(<MovieSettings/>);
wrapper.instance().setState = jest.fn(),
wrapper.find("button").findWhere(e => e.text() === "Cleanup Gravity" && e.type() === "button").simulate("click");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
expect(wrapper.instance().setState).toBeCalledTimes(1);
global.fetch.mockClear();
done();
});
});
}); });

View File

@ -18,6 +18,7 @@ global.prepareFetchApi = (response) => {
const mockJsonPromise = Promise.resolve(response); const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({ const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise, json: () => mockJsonPromise,
text: () => mockJsonPromise
}); });
return (jest.fn().mockImplementation(() => mockFetchPromise)); return (jest.fn().mockImplementation(() => mockFetchPromise));
} }