Merge remote-tracking branch 'origin/stable'

# Conflicts:
#	libraries/ckeditor/ckeditor.js
#	libraries/ckeditor/ckeditor.js.map
This commit is contained in:
zadam 2020-05-29 22:29:28 +02:00
commit d65624d8d2
7 changed files with 83 additions and 22 deletions

View File

@ -24,6 +24,13 @@ const TPL = `
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata) <p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p> for sharing online for debugging purposes without fear of leaking your personal data.</p>
<h4>Backup database</h4>
<button id="backup-database-button" class="btn">Backup database</button>
<br/>
<br/>
<h4>Vacuum database</h4> <h4>Vacuum database</h4>
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p> <p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
@ -37,6 +44,7 @@ export default class AdvancedOptions {
this.$forceFullSyncButton = $("#force-full-sync-button"); this.$forceFullSyncButton = $("#force-full-sync-button");
this.$fillSyncRowsButton = $("#fill-sync-rows-button"); this.$fillSyncRowsButton = $("#fill-sync-rows-button");
this.$anonymizeButton = $("#anonymize-button"); this.$anonymizeButton = $("#anonymize-button");
this.$backupDatabaseButton = $("#backup-database-button");
this.$vacuumDatabaseButton = $("#vacuum-database-button"); this.$vacuumDatabaseButton = $("#vacuum-database-button");
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button"); this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
@ -58,16 +66,22 @@ export default class AdvancedOptions {
toastService.showMessage("Created anonymized database"); toastService.showMessage("Created anonymized database");
}); });
this.$backupDatabaseButton.on('click', async () => {
const {backupFile} = await server.post('database/backup-database');
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
});
this.$vacuumDatabaseButton.on('click', async () => { this.$vacuumDatabaseButton.on('click', async () => {
await server.post('cleanup/vacuum-database'); await server.post('database/vacuum-database');
toastService.showMessage("Database has been vacuumed"); toastService.showMessage("Database has been vacuumed");
}); });
this.$findAndFixConsistencyIssuesButton.on('click', async () => { this.$findAndFixConsistencyIssuesButton.on('click', async () => {
await server.post('cleanup/find-and-fix-consistency-issues'); await server.post('database/find-and-fix-consistency-issues');
toastService.showMessage("Consistency issues should be fixed."); toastService.showMessage("Consistency issues should be fixed.");
}); });
} }
} }

View File

@ -5,17 +5,23 @@ import TypeWidget from "./type_widget.js";
const TPL = ` const TPL = `
<div class="note-detail-file note-detail-printable"> <div class="note-detail-file note-detail-printable">
<style>
.file-table td {
overflow-wrap: anywhere;
}
</style>
<table class="file-table"> <table class="file-table">
<tr> <tr>
<th nowrap>Note ID:</th> <th>Note ID:</th>
<td class="file-note-id"></td> <td class="file-note-id"></td>
<th nowrap>Original file name:</th> <th>Original file name:</th>
<td class="file-filename"></td> <td class="file-filename"></td>
</tr> </tr>
<tr> <tr>
<th nowrap>File type:</th> <th>File type:</th>
<td class="file-filetype"></td> <td class="file-filetype"></td>
<th nowrap>File size:</th> <th>File size:</th>
<td class="file-filesize"></td> <td class="file-filesize"></td>
</tr> </tr>
</table> </table>
@ -94,7 +100,7 @@ export default class FileTypeWidget extends TypeWidget {
toastService.showError("Upload of a new file revision failed."); toastService.showError("Upload of a new file revision failed.");
} }
}); });
return this.$widget; return this.$widget;
} }
@ -130,4 +136,4 @@ export default class FileTypeWidget extends TypeWidget {
getFileUrl() { getFileUrl() {
return utils.getUrlForDownload("api/notes/" + this.noteId + "/download"); return utils.getUrlForDownload("api/notes/" + this.noteId + "/download");
} }
} }

View File

@ -2,8 +2,15 @@
const sql = require('../../services/sql'); const sql = require('../../services/sql');
const log = require('../../services/log'); const log = require('../../services/log');
const backupService = require('../../services/backup');
const consistencyChecksService = require('../../services/consistency_checks'); const consistencyChecksService = require('../../services/consistency_checks');
async function backupDatabase() {
return {
backupFile: await backupService.backupNow("now")
};
}
async function vacuumDatabase() { async function vacuumDatabase() {
await sql.execute("VACUUM"); await sql.execute("VACUUM");
@ -15,6 +22,7 @@ async function findAndFixConsistencyIssues() {
} }
module.exports = { module.exports = {
backupDatabase,
vacuumDatabase, vacuumDatabase,
findAndFixConsistencyIssues findAndFixConsistencyIssues
}; };

View File

@ -16,7 +16,7 @@ const ApiToken = require('../../entities/api_token');
async function loginSync(req) { async function loginSync(req) {
if (!await sqlInit.schemaExists()) { if (!await sqlInit.schemaExists()) {
return [400, { message: "DB schema does not exist, can't sync." }]; return [500, { message: "DB schema does not exist, can't sync." }];
} }
const timestampStr = req.body.timestamp; const timestampStr = req.body.timestamp;
@ -27,7 +27,7 @@ async function loginSync(req) {
// login token is valid for 5 minutes // login token is valid for 5 minutes
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) { if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
return [400, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }]; return [401, { message: 'Auth request time is out of sync, please check that both client and server have correct time.' }];
} }
const syncVersion = req.body.syncVersion; const syncVersion = req.body.syncVersion;
@ -102,4 +102,4 @@ module.exports = {
loginSync, loginSync,
loginToProtectedSession, loginToProtectedSession,
token token
}; };

View File

@ -25,7 +25,7 @@ const importRoute = require('./api/import');
const setupApiRoute = require('./api/setup'); const setupApiRoute = require('./api/setup');
const sqlRoute = require('./api/sql'); const sqlRoute = require('./api/sql');
const anonymizationRoute = require('./api/anonymization'); const anonymizationRoute = require('./api/anonymization');
const cleanupRoute = require('./api/cleanup'); const databaseRoute = require('./api/database');
const imageRoute = require('./api/image'); const imageRoute = require('./api/image');
const attributesRoute = require('./api/attributes'); const attributesRoute = require('./api/attributes');
const scriptRoute = require('./api/script'); const scriptRoute = require('./api/script');
@ -223,10 +223,13 @@ function register(app) {
apiRoute(POST, '/api/sql/execute', sqlRoute.execute); apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize); apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
// VACUUM requires execution outside of transaction // backup requires execution outside of transaction
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false); route(POST, '/api/database/backup-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.backupDatabase, apiResultHandler, false);
route(POST, '/api/cleanup/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.findAndFixConsistencyIssues, apiResultHandler, false); // VACUUM requires execution outside of transaction
route(POST, '/api/database/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.vacuumDatabase, apiResultHandler, false);
route(POST, '/api/database/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.findAndFixConsistencyIssues, apiResultHandler, false);
apiRoute(POST, '/api/script/exec', scriptRoute.exec); apiRoute(POST, '/api/script/exec', scriptRoute.exec);
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run); apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
@ -268,4 +271,4 @@ function register(app) {
module.exports = { module.exports = {
register register
}; };

View File

@ -29,13 +29,38 @@ async function periodBackup(optionName, fileName, periodInSeconds) {
} }
async function backupNow(name) { async function backupNow(name) {
const sql = require('./sql');
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state // we don't want to backup DB in the middle of sync with potentially inconsistent DB state
await syncMutexService.doExclusively(async () => { return await syncMutexService.doExclusively(async () => {
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`; const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
fs.copySync(dataDir.DOCUMENT_PATH, backupFile); try {
fs.unlinkSync(backupFile);
}
catch (e) {} // unlink throws exception if the file did not exist
log.info("Created backup at " + backupFile); let success = false;
let attemptCount = 0
for (; attemptCount < 50 && !success; attemptCount++) {
try {
await sql.executeNoWrap(`VACUUM INTO '${backupFile}'`);
success++;
}
catch (e) {}
// we re-try since VACUUM is very picky and it can't run if there's any other query currently running
// which is difficult to guarantee so we just re-try
}
if (attemptCount === 10) {
log.error(`Creating backup ${backupFile} failed`);
}
else {
log.info("Created backup at " + backupFile);
}
return backupFile;
}); });
} }
@ -52,4 +77,4 @@ sqlInit.dbReady.then(() => {
module.exports = { module.exports = {
backupNow backupNow
}; };

View File

@ -153,6 +153,10 @@ async function execute(query, params = []) {
return await wrap(async db => db.run(query, ...params), query); return await wrap(async db => db.run(query, ...params), query);
} }
async function executeNoWrap(query, params = []) {
await dbConnection.run(query, ...params);
}
async function executeMany(query, params) { async function executeMany(query, params) {
// essentially just alias // essentially just alias
await getManyRows(query, params); await getManyRows(query, params);
@ -264,6 +268,7 @@ module.exports = {
getMap, getMap,
getColumn, getColumn,
execute, execute,
executeNoWrap,
executeMany, executeMany,
executeScript, executeScript,
transactional, transactional,